This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 0f08be57eee [refactor](snapshot) Add cloud snapshot handler SPI and 
Env override (#66916)
0f08be57eee is described below

commit 0f08be57eee4863e81a575172daee0fb2545e91c
Author: Luwei <[email protected]>
AuthorDate: Thu Aug 20 10:36:11 2026 +0800

    [refactor](snapshot) Add cloud snapshot handler SPI and Env override 
(#66916)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: selectdb/selectdb-core#9778
    
    Problem Summary: The open-source FE lacks the extension point used by
    SelectDB Cloud 4.1 to load the enterprise snapshot handler and to expose
    the snapshot-scoped Env. Add the same ServiceLoader fallback and Env
    override to master while preserving the configured handler class as the
    highest-priority compatibility path.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test: Unit Test and full FE build
    - ./run-fe-ut.sh --run
    org.apache.doris.cloud.snapshot.CloudSnapshotHandlerTest
        - ./build.sh --fe -j 48
    - Behavior changed: No. The default open-source handler and Env behavior
    remain unchanged when no extension is installed.
    - Does this need documentation: No
---
 .../main/java/org/apache/doris/catalog/Env.java    |   8 +-
 .../doris/cloud/snapshot/CloudSnapshotHandler.java |  36 ++++-
 .../cloud/snapshot/CloudSnapshotHandlerTest.java   | 145 +++++++++++++++++++++
 3 files changed, 183 insertions(+), 6 deletions(-)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 50c1bfc2171..d2d6eea66c7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -55,6 +55,7 @@ import org.apache.doris.clone.DynamicPartitionScheduler;
 import org.apache.doris.clone.TabletChecker;
 import org.apache.doris.clone.TabletScheduler;
 import org.apache.doris.clone.TabletSchedulerStat;
+import org.apache.doris.cloud.snapshot.CloudSnapshotHandler;
 import org.apache.doris.cloud.system.CloudSystemInfoService;
 import org.apache.doris.common.AnalysisException;
 import org.apache.doris.common.Config;
@@ -941,9 +942,12 @@ public class Env {
                 CHECKPOINT = EnvFactory.getInstance().createEnv(true);
             }
             return CHECKPOINT;
-        } else {
-            return SingletonHolder.INSTANCE;
         }
+        Env snapshotEnv = CloudSnapshotHandler.getSnapshotEnv();
+        if (snapshotEnv != null) {
+            return snapshotEnv;
+        }
+        return SingletonHolder.INSTANCE;
     }
 
     // NOTICE: in most case, we should use getCurrentEnv() to get the right 
catalog.
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandler.java
 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandler.java
index 0b6dc996df5..35905c468db 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandler.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandler.java
@@ -17,6 +17,7 @@
 
 package org.apache.doris.cloud.snapshot;
 
+import org.apache.doris.catalog.Env;
 import org.apache.doris.cloud.proto.Cloud;
 import org.apache.doris.cloud.rpc.MetaServiceProxy;
 import org.apache.doris.common.Config;
@@ -30,24 +31,51 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.lang.reflect.Constructor;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
 
 public class CloudSnapshotHandler extends MasterDaemon {
 
     private static final Logger LOG = 
LogManager.getLogger(CloudSnapshotHandler.class);
+    private static final String DEFAULT_HANDLER_CLASS = 
CloudSnapshotHandler.class.getName();
+    private static volatile Env snapshotEnv;
 
     public CloudSnapshotHandler() {
         super("cloud snapshot handler", 
Config.cloud_snapshot_handler_interval_second * 1000);
     }
 
     public static CloudSnapshotHandler getInstance() {
+        if 
(!DEFAULT_HANDLER_CLASS.equals(Config.cloud_snapshot_handler_class)) {
+            return createByClassName(Config.cloud_snapshot_handler_class);
+        }
+        try {
+            for (CloudSnapshotHandler handler : 
ServiceLoader.load(CloudSnapshotHandler.class)) {
+                return handler;
+            }
+        } catch (ServiceConfigurationError e) {
+            LOG.error("failed to create cloud snapshot handler from service 
loader", e);
+            System.exit(-1);
+            return null;
+        }
+        return new CloudSnapshotHandler();
+    }
+
+    public static Env getSnapshotEnv() {
+        return snapshotEnv;
+    }
+
+    public static void setSnapshotEnv(Env env) {
+        snapshotEnv = env;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static CloudSnapshotHandler createByClassName(String className) {
         try {
-            Class<CloudSnapshotHandler> theClass = 
(Class<CloudSnapshotHandler>) Class.forName(
-                    Config.cloud_snapshot_handler_class);
+            Class<CloudSnapshotHandler> theClass = 
(Class<CloudSnapshotHandler>) Class.forName(className);
             Constructor<CloudSnapshotHandler> constructor = 
theClass.getDeclaredConstructor();
             return constructor.newInstance();
         } catch (Exception e) {
-            LOG.error("failed to create cloud snapshot handler, class name: 
{}", Config.cloud_snapshot_handler_class,
-                    e);
+            LOG.error("failed to create cloud snapshot handler, class name: 
{}", className, e);
             System.exit(-1);
             return null;
         }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandlerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandlerTest.java
new file mode 100644
index 00000000000..337c2c6b6c0
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/cloud/snapshot/CloudSnapshotHandlerTest.java
@@ -0,0 +1,145 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.cloud.snapshot;
+
+import org.apache.doris.catalog.Env;
+import org.apache.doris.common.Config;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Enumeration;
+
+public class CloudSnapshotHandlerTest {
+
+    private String originalHandlerClass;
+    private ClassLoader originalContextClassLoader;
+
+    @Before
+    public void setUp() {
+        originalHandlerClass = Config.cloud_snapshot_handler_class;
+        originalContextClassLoader = 
Thread.currentThread().getContextClassLoader();
+        Config.cloud_snapshot_handler_class = 
CloudSnapshotHandler.class.getName();
+        CloudSnapshotHandler.setSnapshotEnv(null);
+    }
+
+    @After
+    public void tearDown() {
+        Config.cloud_snapshot_handler_class = originalHandlerClass;
+        
Thread.currentThread().setContextClassLoader(originalContextClassLoader);
+        CloudSnapshotHandler.setSnapshotEnv(null);
+    }
+
+    @Test
+    public void testDefaultHandlerWithoutProvider() {
+        Thread.currentThread().setContextClassLoader(new 
SnapshotHandlerClassLoader(null));
+        CloudSnapshotHandler handler = CloudSnapshotHandler.getInstance();
+
+        Assert.assertEquals(CloudSnapshotHandler.class, handler.getClass());
+    }
+
+    @Test
+    public void testLoadHandlerFromServiceProvider() {
+        Thread.currentThread().setContextClassLoader(
+                new 
SnapshotHandlerClassLoader(ServiceLoadedSnapshotHandler.class));
+        CloudSnapshotHandler handler = CloudSnapshotHandler.getInstance();
+
+        Assert.assertTrue(handler instanceof ServiceLoadedSnapshotHandler);
+    }
+
+    @Test
+    public void testConfiguredHandlerTakesPrecedenceOverServiceProvider() {
+        Config.cloud_snapshot_handler_class = 
ConfiguredSnapshotHandler.class.getName();
+        Thread.currentThread().setContextClassLoader(
+                new 
SnapshotHandlerClassLoader(ServiceLoadedSnapshotHandler.class));
+
+        CloudSnapshotHandler handler = CloudSnapshotHandler.getInstance();
+
+        Assert.assertTrue(handler instanceof ConfiguredSnapshotHandler);
+    }
+
+    @Test
+    public void testSnapshotEnvOverridesCurrentEnv() {
+        Env snapshotEnv = Mockito.mock(Env.class);
+
+        CloudSnapshotHandler.setSnapshotEnv(snapshotEnv);
+
+        Assert.assertSame(snapshotEnv, CloudSnapshotHandler.getSnapshotEnv());
+        Assert.assertSame(snapshotEnv, Env.getCurrentEnv());
+    }
+
+    public static class ServiceLoadedSnapshotHandler extends 
CloudSnapshotHandler {
+    }
+
+    public static class ConfiguredSnapshotHandler extends CloudSnapshotHandler 
{
+    }
+
+    private static class SnapshotHandlerClassLoader extends ClassLoader {
+
+        private static final String SERVICE_FILE =
+                "META-INF/services/" + CloudSnapshotHandler.class.getName();
+
+        private final Class<? extends CloudSnapshotHandler> providerClass;
+
+        SnapshotHandlerClassLoader(Class<? extends CloudSnapshotHandler> 
providerClass) {
+            super(CloudSnapshotHandlerTest.class.getClassLoader());
+            this.providerClass = providerClass;
+        }
+
+        @Override
+        public Enumeration<URL> getResources(String name) {
+            if (!SERVICE_FILE.equals(name) || providerClass == null) {
+                return Collections.emptyEnumeration();
+            }
+            return 
Collections.enumeration(Collections.singletonList(serviceFileUrl()));
+        }
+
+        private URL serviceFileUrl() {
+            byte[] content = 
providerClass.getName().getBytes(StandardCharsets.UTF_8);
+            try {
+                return new URL("synthetic", "", -1, SERVICE_FILE, new 
URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) {
+                        return new URLConnection(url) {
+                            @Override
+                            public void connect() {
+                            }
+
+                            @Override
+                            public InputStream getInputStream() {
+                                return new ByteArrayInputStream(content);
+                            }
+                        };
+                    }
+                });
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to