You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by GitBox <gi...@apache.org> on 2018/06/04 02:02:52 UTC

[GitHub] wujimin closed pull request #731: [SCB-617] Graceful shutdown with standalone tomcat

wujimin closed pull request #731: [SCB-617] Graceful shutdown with standalone tomcat
URL: https://github.com/apache/incubator-servicecomb-java-chassis/pull/731
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/core/src/main/java/org/apache/servicecomb/core/CseApplicationListener.java b/core/src/main/java/org/apache/servicecomb/core/CseApplicationListener.java
index 365868746..38793cd10 100644
--- a/core/src/main/java/org/apache/servicecomb/core/CseApplicationListener.java
+++ b/core/src/main/java/org/apache/servicecomb/core/CseApplicationListener.java
@@ -28,6 +28,7 @@
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.ApplicationEvent;
 import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.context.support.AbstractApplicationContext;
 import org.springframework.core.Ordered;
@@ -71,6 +72,8 @@ public void onApplicationEvent(ApplicationEvent event) {
       }
 
       SCBEngine.getInstance().init();
+    } else if (event instanceof ContextClosedEvent) {
+      SCBEngine.getInstance().destroy();
     }
   }
 }
diff --git a/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java b/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java
index d30eb3c07..4d2f15702 100644
--- a/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java
+++ b/core/src/main/java/org/apache/servicecomb/core/SCBEngine.java
@@ -20,6 +20,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
+import org.apache.servicecomb.config.ConfigUtil;
 import org.apache.servicecomb.core.BootListener.BootEvent;
 import org.apache.servicecomb.core.BootListener.EventType;
 import org.apache.servicecomb.core.definition.loader.SchemaListenerManager;
@@ -177,7 +178,7 @@ public synchronized void init() {
         doInit();
         status = SCBStatus.UP;
       } catch (Exception e) {
-        uninit();
+        destroy();
         status = SCBStatus.FAILED;
         throw new IllegalStateException("ServiceComb init failed.", e);
       }
@@ -217,23 +218,23 @@ private void doInit() throws Exception {
 
     RegistryUtils.run();
 
-    Runtime.getRuntime().addShutdownHook(new Thread(this::uninit));
+    Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
   }
 
   /**
    * not allow throw any exception
    * even some step throw exception, must catch it and go on, otherwise shutdown process will be broken.
    */
-  public synchronized void uninit() {
+  public synchronized void destroy() {
     if (SCBStatus.UP.equals(status)) {
       LOGGER.info("ServiceComb is closing now...");
-      doUninit();
+      doDestroy();
       status = SCBStatus.DOWN;
       LOGGER.info("ServiceComb had closed");
     }
   }
 
-  private void doUninit() {
+  private void doDestroy() {
     //Step 1: notify all component stop invoke via BEFORE_CLOSE Event
     safeTriggerEvent(EventType.BEFORE_CLOSE);
 
@@ -256,7 +257,10 @@ private void doUninit() {
     VertxUtils.blockCloseVertxByName("config-center");
     VertxUtils.blockCloseVertxByName("transport");
 
-    //Step 6: notify all component do clean works via AFTER_CLOSE Event
+    //Step 6: destroy config center source
+    ConfigUtil.destroyConfigCenterConfigurationSource();
+
+    //Step 7: notify all component do clean works via AFTER_CLOSE Event
     safeTriggerEvent(EventType.AFTER_CLOSE);
   }
 
diff --git a/core/src/main/java/org/apache/servicecomb/core/executor/FixedThreadExecutor.java b/core/src/main/java/org/apache/servicecomb/core/executor/FixedThreadExecutor.java
index 2600ebb53..8ea95dff4 100644
--- a/core/src/main/java/org/apache/servicecomb/core/executor/FixedThreadExecutor.java
+++ b/core/src/main/java/org/apache/servicecomb/core/executor/FixedThreadExecutor.java
@@ -17,10 +17,12 @@
 
 package org.apache.servicecomb.core.executor;
 
+import java.io.Closeable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -30,7 +32,7 @@
 
 import com.netflix.config.DynamicPropertyFactory;
 
-public class FixedThreadExecutor implements Executor {
+public class FixedThreadExecutor implements Executor, Closeable {
   private static final Logger LOGGER = LoggerFactory.getLogger(FixedThreadExecutor.class);
 
   public static final String KEY_GROUP = "servicecomb.executor.default.group";
@@ -38,7 +40,7 @@
   public static final String KEY_THREAD = "servicecomb.executor.default.thread-per-group";
 
   // to avoid multiple network thread conflicted when put tasks to executor queue
-  private List<Executor> executorList = new ArrayList<>();
+  private List<ExecutorService> executorList = new ArrayList<>();
 
   // for bind network thread to one executor
   // it's impossible that has too many network thread, so index will not too big that less than 0
@@ -58,7 +60,7 @@ public FixedThreadExecutor() {
     }
   }
 
-  public List<Executor> getExecutorList() {
+  public List<ExecutorService> getExecutorList() {
     return executorList;
   }
 
@@ -74,4 +76,12 @@ private Executor chooseExecutor(long threadId) {
     int idx = index.getAndIncrement() % executorList.size();
     return executorList.get(idx);
   }
+
+  @Override
+  public void close() {
+    for (ExecutorService executorService : executorList) {
+      executorService.shutdown();
+    }
+    executorList.clear();
+  }
 }
diff --git a/core/src/main/java/org/apache/servicecomb/core/provider/producer/ProducerProviderManager.java b/core/src/main/java/org/apache/servicecomb/core/provider/producer/ProducerProviderManager.java
index 35952964f..b9b6b415c 100644
--- a/core/src/main/java/org/apache/servicecomb/core/provider/producer/ProducerProviderManager.java
+++ b/core/src/main/java/org/apache/servicecomb/core/provider/producer/ProducerProviderManager.java
@@ -17,39 +17,72 @@
 
 package org.apache.servicecomb.core.provider.producer;
 
+import java.io.Closeable;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
 
 import javax.inject.Inject;
 
+import org.apache.commons.io.IOUtils;
+import org.apache.servicecomb.core.BootListener;
 import org.apache.servicecomb.core.ProducerProvider;
 import org.apache.servicecomb.core.definition.MicroserviceMeta;
 import org.apache.servicecomb.core.definition.MicroserviceMetaManager;
+import org.apache.servicecomb.core.definition.OperationMeta;
 import org.apache.servicecomb.core.definition.SchemaMeta;
 import org.apache.servicecomb.core.definition.SchemaUtils;
 import org.apache.servicecomb.serviceregistry.RegistryUtils;
 import org.apache.servicecomb.serviceregistry.api.registry.Microservice;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
-public class ProducerProviderManager {
+public class ProducerProviderManager implements BootListener {
+  private static final Logger LOGGER = LoggerFactory.getLogger(ProducerProviderManager.class);
+
   @Autowired(required = false)
   private List<ProducerProvider> producerProviderList = Collections.emptyList();
 
   @Inject
   private MicroserviceMetaManager microserviceMetaManager;
 
+  private MicroserviceMeta microserviceMeta;
+
   public void init() throws Exception {
     for (ProducerProvider provider : producerProviderList) {
       provider.init();
     }
 
     Microservice microservice = RegistryUtils.getMicroservice();
-    MicroserviceMeta microserviceMeta = microserviceMetaManager.getOrCreateMicroserviceMeta(microservice);
+    microserviceMeta = microserviceMetaManager.getOrCreateMicroserviceMeta(microservice);
     for (SchemaMeta schemaMeta : microserviceMeta.getSchemaMetas()) {
       String content = SchemaUtils.swaggerToString(schemaMeta.getSwagger());
       microservice.addSchema(schemaMeta.getSchemaId(), content);
     }
   }
+
+  @Override
+  public void onBootEvent(BootEvent event) {
+    if (!EventType.AFTER_CLOSE.equals(event.getEventType())) {
+      return;
+    }
+
+    for (OperationMeta operationMeta : microserviceMeta.getOperations()) {
+      if (ExecutorService.class.isInstance(operationMeta.getExecutor())) {
+        ((ExecutorService) operationMeta.getExecutor()).shutdown();
+        continue;
+      }
+
+      if (Closeable.class.isInstance(operationMeta.getExecutor())) {
+        IOUtils.closeQuietly((Closeable) operationMeta.getExecutor());
+        continue;
+      }
+
+      LOGGER.warn("Executor {} do not support close or shutdown, it may block service shutdown.",
+          operationMeta.getExecutor().getClass().getName());
+    }
+  }
 }
diff --git a/core/src/test/java/org/apache/servicecomb/core/TestSCBEngine.java b/core/src/test/java/org/apache/servicecomb/core/TestSCBEngine.java
index e452e55e3..6b72f566b 100644
--- a/core/src/test/java/org/apache/servicecomb/core/TestSCBEngine.java
+++ b/core/src/test/java/org/apache/servicecomb/core/TestSCBEngine.java
@@ -18,7 +18,9 @@
 package org.apache.servicecomb.core;
 
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.servicecomb.config.ConfigUtil;
 import org.apache.servicecomb.core.definition.loader.SchemaListenerManager;
 import org.apache.servicecomb.core.provider.consumer.ConsumerProviderManager;
 import org.apache.servicecomb.core.provider.consumer.ReferenceConfig;
@@ -36,6 +38,8 @@
 
 import mockit.Expectations;
 import mockit.Injectable;
+import mockit.Mock;
+import mockit.MockUp;
 import mockit.Mocked;
 
 public class TestSCBEngine {
@@ -47,7 +51,6 @@ public void test(@Injectable ProducerProviderManager producerProviderManager,
       @Injectable ConsumerProviderManager consumerProviderManager,
       @Injectable TransportManager transportManager,
       @Injectable AppManager appManager) {
-
     new Expectations(RegistryUtils.class) {
       {
         RegistryUtils.getServiceRegistry().getAppManager();
@@ -56,6 +59,13 @@ public void test(@Injectable ProducerProviderManager producerProviderManager,
         RegistryUtils.destroy();
       }
     };
+    AtomicBoolean configDestroy = new AtomicBoolean();
+    new MockUp<ConfigUtil>() {
+      @Mock
+      void destroyConfigCenterConfigurationSource() {
+        configDestroy.set(true);
+      }
+    };
 
     SchemaListenerManager schemaListenerManager = Mockito.mock(SchemaListenerManager.class);
 
@@ -72,9 +82,10 @@ public void test(@Injectable ProducerProviderManager producerProviderManager,
 
     Assert.assertEquals(SCBStatus.UP, engine.getStatus());
 
-    engine.uninit();
+    engine.destroy();
 
     Assert.assertEquals(SCBStatus.DOWN, engine.getStatus());
+    Assert.assertTrue(configDestroy.get());
   }
 
   @Test
diff --git a/core/src/test/java/org/apache/servicecomb/core/org/apache/servicecomb/core/TestCseApplicationListener.java b/core/src/test/java/org/apache/servicecomb/core/org/apache/servicecomb/core/TestCseApplicationListener.java
new file mode 100644
index 000000000..aeac7e1f5
--- /dev/null
+++ b/core/src/test/java/org/apache/servicecomb/core/org/apache/servicecomb/core/TestCseApplicationListener.java
@@ -0,0 +1,56 @@
+/*
+ * 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.servicecomb.core.org.apache.servicecomb.core;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.servicecomb.core.CseApplicationListener;
+import org.apache.servicecomb.core.SCBEngine;
+import org.apache.servicecomb.core.SCBStatus;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.event.ContextClosedEvent;
+
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.Mocked;
+
+public class TestCseApplicationListener {
+  @Test
+  public void onApplicationEvent_close(@Mocked ContextClosedEvent contextClosedEvent) {
+    AtomicInteger count = new AtomicInteger();
+    SCBEngine scbEngine = new SCBEngine() {
+      @Override
+      public synchronized void destroy() {
+        count.incrementAndGet();
+      }
+    };
+    new MockUp<SCBEngine>() {
+      @Mock
+      SCBEngine getInstance() {
+        return scbEngine;
+      }
+    };
+    scbEngine.setStatus(SCBStatus.UP);
+
+    CseApplicationListener listener = new CseApplicationListener();
+
+    listener.onApplicationEvent(contextClosedEvent);
+
+    Assert.assertEquals(1, count.get());
+  }
+}
diff --git a/core/src/test/java/org/apache/servicecomb/core/provider/producer/TestProducerProviderManager.java b/core/src/test/java/org/apache/servicecomb/core/provider/producer/TestProducerProviderManager.java
index 79c0832c8..f082240ce 100644
--- a/core/src/test/java/org/apache/servicecomb/core/provider/producer/TestProducerProviderManager.java
+++ b/core/src/test/java/org/apache/servicecomb/core/provider/producer/TestProducerProviderManager.java
@@ -17,10 +17,28 @@
 
 package org.apache.servicecomb.core.provider.producer;
 
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.servicecomb.core.BootListener.BootEvent;
+import org.apache.servicecomb.core.BootListener.EventType;
+import org.apache.servicecomb.core.definition.MicroserviceMeta;
 import org.apache.servicecomb.core.definition.MicroserviceMetaManager;
+import org.apache.servicecomb.core.definition.OperationMeta;
+import org.apache.servicecomb.core.executor.FixedThreadExecutor;
+import org.apache.servicecomb.core.executor.ReactiveExecutor;
+import org.apache.servicecomb.foundation.test.scaffolding.log.LogCollector;
+import org.junit.Assert;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
+import mockit.Deencapsulation;
+import mockit.Expectations;
+import mockit.Mock;
+import mockit.MockUp;
 import mockit.Mocked;
 
 public class TestProducerProviderManager {
@@ -34,4 +52,77 @@ public void allowedNoProvider(@Mocked MicroserviceMetaManager microserviceMetaMa
 
     context.close();
   }
+
+  @Test
+  public void onBootEvent_notClose() {
+    BootEvent event = new BootEvent();
+    event.setEventType(EventType.BEFORE_CLOSE);
+
+    ProducerProviderManager producerProviderManager = new ProducerProviderManager();
+    // should not throw exception
+    producerProviderManager.onBootEvent(event);
+  }
+
+  @Test
+  public void onBootEvent_close(@Mocked MicroserviceMeta microserviceMeta, @Mocked OperationMeta op1,
+      @Mocked OperationMeta op2, @Mocked FixedThreadExecutor closeable) {
+    AtomicInteger count = new AtomicInteger();
+    ExecutorService executorService = new MockUp<ExecutorService>() {
+      @Mock
+      void shutdown() {
+        count.incrementAndGet();
+      }
+    }.getMockInstance();
+    new MockUp<Closeable>(closeable) {
+      @Mock
+      void close() {
+        count.incrementAndGet();
+      }
+    };
+    new Expectations() {
+      {
+        microserviceMeta.getOperations();
+        result = Arrays.asList(op1, op2);
+        op1.getExecutor();
+        result = executorService;
+        op2.getExecutor();
+        result = closeable;
+      }
+    };
+    BootEvent event = new BootEvent();
+    event.setEventType(EventType.AFTER_CLOSE);
+
+    ProducerProviderManager producerProviderManager = new ProducerProviderManager();
+    Deencapsulation.setField(producerProviderManager, "microserviceMeta", microserviceMeta);
+
+    producerProviderManager.onBootEvent(event);
+
+    Assert.assertEquals(2, count.get());
+  }
+
+  @Test
+  public void onBootEvent_close_unknown(@Mocked MicroserviceMeta microserviceMeta, @Mocked OperationMeta op1) {
+    Executor executor = new ReactiveExecutor();
+    new Expectations() {
+      {
+        microserviceMeta.getOperations();
+        result = Arrays.asList(op1);
+        op1.getExecutor();
+        result = executor;
+      }
+    };
+    LogCollector logCollector = new LogCollector();
+    BootEvent event = new BootEvent();
+    event.setEventType(EventType.AFTER_CLOSE);
+
+    ProducerProviderManager producerProviderManager = new ProducerProviderManager();
+    Deencapsulation.setField(producerProviderManager, "microserviceMeta", microserviceMeta);
+
+    producerProviderManager.onBootEvent(event);
+
+    Assert.assertEquals(
+        "Executor org.apache.servicecomb.core.executor.ReactiveExecutor do not support close or shutdown, it may block service shutdown.",
+        logCollector.getEvents().get(0).getMessage());
+    logCollector.teardown();
+  }
 }
diff --git a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/archaius/sources/ConfigCenterConfigurationSourceImpl.java b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/archaius/sources/ConfigCenterConfigurationSourceImpl.java
index d4392994f..f4c12a726 100644
--- a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/archaius/sources/ConfigCenterConfigurationSourceImpl.java
+++ b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/archaius/sources/ConfigCenterConfigurationSourceImpl.java
@@ -49,6 +49,8 @@
 
   private UpdateHandler updateHandler = new UpdateHandler();
 
+  private ConfigCenterClient configCenterClient;
+
   private static final String CONFIG_CENTER_URL_KEY = "cse.config.client.serverUri";
 
   public ConfigCenterConfigurationSourceImpl() {
@@ -64,7 +66,7 @@ public boolean isValidSource(Configuration localConfiguration) {
   }
 
   private void init() {
-    ConfigCenterClient configCenterClient = new ConfigCenterClient(updateHandler);
+    configCenterClient = new ConfigCenterClient(updateHandler);
     configCenterClient.connectServer();
   }
 
@@ -74,6 +76,15 @@ public void init(Configuration localConfiguration) {
     init();
   }
 
+  @Override
+  public void destroy() {
+    if (configCenterClient == null) {
+      return;
+    }
+
+    configCenterClient.destroy();
+  }
+
   public void addUpdateListener(WatchedUpdateListener watchedUpdateListener) {
     if (watchedUpdateListener != null) {
       listeners.add(watchedUpdateListener);
diff --git a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
index 9b78429d5..9941a27de 100644
--- a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
+++ b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/ConfigCenterClient.java
@@ -83,12 +83,12 @@
 
   private static final String SSL_KEY = "cc.consumer";
 
-  private static final ScheduledExecutorService EXECUTOR = Executors.newScheduledThreadPool(1);
-
   private static final long HEARTBEAT_INTERVAL = 30000;
 
   private static final long BOOTUP_WAIT_TIME = 10;
 
+  private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
+
   private ScheduledExecutorService heartbeatTask = null;
 
   private int refreshMode = CONFIG_CENTER_CONFIG.getRefreshMode();
@@ -109,13 +109,15 @@
 
   private ConfigCenterConfigurationSourceImpl.UpdateHandler updateHandler;
 
-  private static ClientPoolManager<HttpClientWithContext> clientMgr;
+  private ClientPoolManager<HttpClientWithContext> clientMgr;
 
   private boolean isWatching = false;
 
-  private static final ServiceLoader<AuthHeaderProvider> authHeaderProviders =
+  private final ServiceLoader<AuthHeaderProvider> authHeaderProviders =
       ServiceLoader.load(AuthHeaderProvider.class);
 
+  private URIConst uriConst = new URIConst();
+
   public ConfigCenterClient(ConfigCenterConfigurationSourceImpl.UpdateHandler updateHandler) {
     this.updateHandler = updateHandler;
   }
@@ -134,18 +136,30 @@ public void connectServer() {
     refreshMembers(memberDiscovery);
     ConfigRefresh refreshTask = new ConfigRefresh(parseConfigUtils, memberDiscovery);
     refreshTask.run(true);
-    EXECUTOR.scheduleWithFixedDelay(refreshTask,
+    executor.scheduleWithFixedDelay(refreshTask,
         firstRefreshInterval,
         refreshInterval,
         TimeUnit.MILLISECONDS);
   }
 
+  public void destroy() {
+    if (executor != null) {
+      executor.shutdown();
+      executor = null;
+    }
+
+    if (heartbeatTask != null) {
+      heartbeatTask.shutdown();
+      heartbeatTask = null;
+    }
+  }
+
   private void refreshMembers(MemberDiscovery memberDiscovery) {
     if (CONFIG_CENTER_CONFIG.getAutoDiscoveryEnabled()) {
       String configCenter = memberDiscovery.getConfigServer();
       IpPort ipPort = NetUtils.parseIpPortFromURI(configCenter);
       clientMgr.findThreadBindClientPool().runOnContext(client -> {
-        HttpClientRequest request = client.get(ipPort.getPort(), ipPort.getHostOrIp(), URIConst.MEMBERS, rsp -> {
+        HttpClientRequest request = client.get(ipPort.getPort(), ipPort.getHostOrIp(), uriConst.MEMBERS, rsp -> {
           if (rsp.statusCode() == HttpResponseStatus.OK.code()) {
             rsp.bodyHandler(buf -> {
               memberDiscovery.refreshMembers(buf.toJsonObject());
@@ -153,7 +167,7 @@ private void refreshMembers(MemberDiscovery memberDiscovery) {
           }
         });
         SignRequest signReq = createSignRequest(request.method().toString(),
-            configCenter + URIConst.MEMBERS,
+            configCenter + uriConst.MEMBERS,
             new HashMap<>(),
             null);
         if (ConfigCenterConfig.INSTANCE.getToken() != null) {
@@ -247,7 +261,7 @@ public void doWatch(String configCenter)
         throws UnsupportedEncodingException, InterruptedException {
       CountDownLatch waiter = new CountDownLatch(1);
       IpPort ipPort = NetUtils.parseIpPortFromURI(configCenter);
-      String url = URIConst.REFRESH_ITEMS + "?dimensionsInfo="
+      String url = uriConst.REFRESH_ITEMS + "?dimensionsInfo="
           + StringUtils.deleteWhitespace(URLEncoder.encode(serviceName, "UTF-8"));
       Map<String, String> headers = new HashMap<>();
       headers.put("x-domain-name", tenantName);
@@ -337,7 +351,7 @@ public void refreshConfig(String configcenter, boolean wait) {
         LOGGER.error("encode failed. Error message: {}", e.getMessage());
         encodeServiceName = StringUtils.deleteWhitespace(serviceName);
       }
-      String path = URIConst.ITEMS + "?dimensionsInfo=" + encodeServiceName;
+      String path = uriConst.ITEMS + "?dimensionsInfo=" + encodeServiceName;
       clientMgr.findThreadBindClientPool().runOnContext(client -> {
         IpPort ipPort = NetUtils.parseIpPortFromURI(configcenter);
         HttpClientRequest request = client.get(ipPort.getPort(), ipPort.getHostOrIp(), path, rsp -> {
diff --git a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/URIConst.java b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/URIConst.java
index 331c74425..14be67163 100644
--- a/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/URIConst.java
+++ b/dynamic-config/config-cc/src/main/java/org/apache/servicecomb/config/client/URIConst.java
@@ -18,41 +18,25 @@
 package org.apache.servicecomb.config.client;
 
 public class URIConst {
-  public static final String DOMAIN_NAME = ConfigCenterConfig.INSTANCE.getDomainName();
+  private final String DOMAIN_NAME = ConfigCenterConfig.INSTANCE.getDomainName();
 
-  public static final String CURRENT_VERSION = ConfigCenterConfig.INSTANCE.getApiVersion();
+  private final String CURRENT_VERSION = ConfigCenterConfig.INSTANCE.getApiVersion();
 
-  public static final String VERSION_V2 = "v2";
+  private final String VERSION_V2 = "v2";
 
-  public static final String PREFIX_V3 = String.format("/v3/%s/configuration", DOMAIN_NAME);
+  private final String PREFIX_V2 = "/configuration/v2";
 
-  public static final String MEMBERS;
+  private final String PREFIX_V3 = String.format("/v3/%s/configuration", DOMAIN_NAME);
 
-  static {
-    if (VERSION_V2.equals(CURRENT_VERSION)) {
-      MEMBERS = "/members";
-    } else {
-      MEMBERS = PREFIX_V3 + "/members";
-    }
-  }
+  private final boolean isV2 = VERSION_V2.equals(CURRENT_VERSION);
 
-  public static final String REFRESH_ITEMS;
+  private final String CURRENT_PREFIX = isV2 ? PREFIX_V2 : PREFIX_V3;
 
-  static {
-    if (VERSION_V2.equals(CURRENT_VERSION)) {
-      REFRESH_ITEMS = "/configuration/v2/refresh/items";
-    } else {
-      REFRESH_ITEMS = PREFIX_V3 + "/refresh/items";
-    }
-  }
+  // v2 no prefix
+  public final String MEMBERS = isV2 ? "/members" : PREFIX_V3 + "/members";
 
-  public static final String ITEMS;
+  // v2 has prefix
+  public final String REFRESH_ITEMS = CURRENT_PREFIX + "/refresh/items";
 
-  static {
-    if (VERSION_V2.equals(CURRENT_VERSION)) {
-      ITEMS = "/configuration/v2/items";
-    } else {
-      ITEMS = PREFIX_V3 + "/items";
-    }
-  }
+  public final String ITEMS = CURRENT_PREFIX + "/items";
 }
diff --git a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/archaius/sources/TestConfigCenterConfigurationSource.java b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/archaius/sources/TestConfigCenterConfigurationSource.java
index 728c26233..ec4512749 100644
--- a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/archaius/sources/TestConfigCenterConfigurationSource.java
+++ b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/archaius/sources/TestConfigCenterConfigurationSource.java
@@ -18,8 +18,11 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.commons.lang.reflect.FieldUtils;
 import org.apache.servicecomb.config.archaius.sources.ConfigCenterConfigurationSourceImpl.UpdateHandler;
+import org.apache.servicecomb.config.client.ConfigCenterClient;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -28,6 +31,8 @@
 import com.netflix.config.WatchedUpdateResult;
 
 import mockit.Deencapsulation;
+import mockit.Mock;
+import mockit.MockUp;
 
 /**
  * Created by on 2017/1/12.
@@ -91,4 +96,29 @@ public void testRemoveUpdateListener() {
     configCenterSource.removeUpdateListener(watchedUpdateListener);
     Assert.assertTrue(configCenterSource.getCurrentListeners().isEmpty());
   }
+
+  @Test
+  public void destroy_notInit() {
+    ConfigCenterConfigurationSourceImpl configCenterSource = new ConfigCenterConfigurationSourceImpl();
+
+    // need not throw exception
+    configCenterSource.destroy();
+  }
+
+  @Test
+  public void destroy_inited() throws IllegalAccessException {
+    AtomicInteger count = new AtomicInteger();
+    ConfigCenterClient configCenterClient = new MockUp<ConfigCenterClient>() {
+      @Mock
+      void destroy() {
+        count.incrementAndGet();
+      }
+    }.getMockInstance();
+    ConfigCenterConfigurationSourceImpl configCenterSource = new ConfigCenterConfigurationSourceImpl();
+    FieldUtils.writeDeclaredField(configCenterSource, "configCenterClient", configCenterClient, true);
+
+    configCenterSource.destroy();
+
+    Assert.assertEquals(1, count.get());
+  }
 }
diff --git a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestConfigCenterClient.java b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestConfigCenterClient.java
index 0f9013c59..83398f107 100644
--- a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestConfigCenterClient.java
+++ b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestConfigCenterClient.java
@@ -20,12 +20,15 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.servicecomb.config.ConfigUtil;
 import org.apache.servicecomb.config.archaius.sources.ConfigCenterConfigurationSourceImpl;
 import org.apache.servicecomb.config.archaius.sources.ConfigCenterConfigurationSourceImpl.UpdateHandler;
 import org.apache.servicecomb.config.client.ConfigCenterClient.ConfigRefresh;
 import org.apache.servicecomb.foundation.common.event.EventManager;
+import org.apache.servicecomb.foundation.vertx.client.ClientPoolManager;
 import org.apache.servicecomb.foundation.vertx.client.http.HttpClientWithContext;
 import org.apache.servicecomb.foundation.vertx.client.http.HttpClientWithContext.RunHandler;
 import org.junit.Assert;
@@ -45,8 +48,10 @@
 import io.vertx.core.http.WebSocket;
 import io.vertx.core.json.JsonObject;
 import mockit.Deencapsulation;
+import mockit.Expectations;
 import mockit.Mock;
 import mockit.MockUp;
+import mockit.Mocked;
 
 public class TestConfigCenterClient {
   @BeforeClass
@@ -110,9 +115,9 @@ public void testConnectRefreshModeTwo() {
 
   @SuppressWarnings("unchecked")
   @Test
-  public void testConfigRefresh() {
+  public void testConfigRefresh(@Mocked ClientPoolManager<HttpClientWithContext> clientMgr,
+      @Mocked HttpClientWithContext httpClientWithContext) {
     ConfigCenterConfigurationSourceImpl impl = new ConfigCenterConfigurationSourceImpl();
-    impl.init(ConfigUtil.createLocalConfig());
     UpdateHandler updateHandler = impl.new UpdateHandler();
     HttpClientRequest request = Mockito.mock(HttpClientRequest.class);
     Mockito.when(request.headers()).thenReturn(MultiMap.caseInsensitiveMultiMap());
@@ -120,13 +125,13 @@ public void testConfigRefresh() {
     Mockito.when(rsp.toString())
         .thenReturn("{\"application\":{\"2\":\"2\",\"aa\":\"1\"},\"vmalledge\":{\"aa\":\"3\"}}");
 
-    HttpClientResponse event = Mockito.mock(HttpClientResponse.class);
-    Mockito.when(event.bodyHandler(Mockito.any(Handler.class))).then(invocation -> {
+    HttpClientResponse httpClientResponse = Mockito.mock(HttpClientResponse.class);
+    Mockito.when(httpClientResponse.bodyHandler(Mockito.any(Handler.class))).then(invocation -> {
       Handler<Buffer> handler = invocation.getArgumentAt(0, Handler.class);
       handler.handle(rsp);
       return null;
     });
-    Mockito.when(event.statusCode()).thenReturn(200);
+    Mockito.when(httpClientResponse.statusCode()).thenReturn(200);
 
     Buffer buf = Mockito.mock(Buffer.class);
     Mockito.when(buf.toJsonObject()).thenReturn(new JsonObject(
@@ -142,7 +147,7 @@ public void testConfigRefresh() {
         httpClient.get(Mockito.anyInt(), Mockito.anyString(), Mockito.anyString(), Mockito.any(Handler.class)))
         .then(invocation -> {
           Handler<HttpClientResponse> handler = invocation.getArgumentAt(3, Handler.class);
-          handler.handle(event);
+          handler.handle(httpClientResponse);
           return request;
         });
     Mockito.when(httpClient.websocket(Mockito.anyInt(),
@@ -162,7 +167,15 @@ public void runOnContext(RunHandler handler) {
         handler.run(httpClient);
       }
     };
+    new Expectations() {
+      {
+        clientMgr.findThreadBindClientPool();
+        result = httpClientWithContext;
+      }
+    };
+
     ConfigCenterClient cc = new ConfigCenterClient(updateHandler);
+    Deencapsulation.setField(cc, "clientMgr", clientMgr);
     ParseConfigUtils parseConfigUtils = new ParseConfigUtils(updateHandler);
     MemberDiscovery memberdis = new MemberDiscovery(Arrays.asList("http://configcentertest:30103"));
     ConfigRefresh refresh = cc.new ConfigRefresh(parseConfigUtils, memberdis);
@@ -175,7 +188,8 @@ public void runOnContext(RunHandler handler) {
 
   @SuppressWarnings("unchecked")
   @Test
-  public void testConfigRefreshException() {
+  public void testConfigRefreshException(@Mocked ClientPoolManager<HttpClientWithContext> clientMgr,
+      @Mocked HttpClientWithContext httpClientWithContext) {
     ConfigCenterConfigurationSourceImpl impl = new ConfigCenterConfigurationSourceImpl();
     Map<String, String> map = new HashMap<>();
     EventManager.register(new Object() {
@@ -189,7 +203,6 @@ public void testMsg(Object event) {
         }
       }
     });
-    impl.init(ConfigUtil.createLocalConfig());
     UpdateHandler updateHandler = impl.new UpdateHandler();
     HttpClientRequest request = Mockito.mock(HttpClientRequest.class);
     Mockito.when(request.headers()).thenReturn(MultiMap.caseInsensitiveMultiMap());
@@ -221,7 +234,14 @@ public void runOnContext(RunHandler handler) {
         handler.run(httpClient);
       }
     };
+    new Expectations() {
+      {
+        clientMgr.findThreadBindClientPool();
+        result = httpClientWithContext;
+      }
+    };
     ConfigCenterClient cc = new ConfigCenterClient(updateHandler);
+    Deencapsulation.setField(cc, "clientMgr", clientMgr);
     ParseConfigUtils parseConfigUtils = new ParseConfigUtils(updateHandler);
     MemberDiscovery memberdis = new MemberDiscovery(Arrays.asList("http://configcentertest:30103"));
     ConfigRefresh refresh = cc.new ConfigRefresh(parseConfigUtils, memberdis);
@@ -231,4 +251,19 @@ public void runOnContext(RunHandler handler) {
     refresh.run();
     Assert.assertEquals("Succ event trigger", map.get("result"));
   }
+
+  @Test
+  public void destroy() {
+    ConfigCenterClient configCenterClient = new ConfigCenterClient(null);
+    ScheduledExecutorService executor = Deencapsulation.getField(configCenterClient, "executor");
+    ScheduledExecutorService heartbeatTask = Executors.newScheduledThreadPool(1);
+    Deencapsulation.setField(configCenterClient, "heartbeatTask", heartbeatTask);
+
+    Assert.assertFalse(executor.isShutdown());
+
+    configCenterClient.destroy();
+
+    Assert.assertTrue(executor.isShutdown());
+    Assert.assertTrue(heartbeatTask.isShutdown());
+  }
 }
diff --git a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestURIConst.java b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestURIConst.java
index 51eeffb10..8877624b0 100644
--- a/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestURIConst.java
+++ b/dynamic-config/config-cc/src/test/java/org/apache/servicecomb/config/client/TestURIConst.java
@@ -25,7 +25,24 @@
 
 public class TestURIConst {
   @Test
-  public void testURI(final @Mocked ConfigCenterConfig config) {
+  public void testURI_v2(final @Mocked ConfigCenterConfig config) {
+    new Expectations() {
+      {
+        config.getDomainName();
+        result = "mytenant";
+        config.getApiVersion();
+        result = "v2";
+      }
+    };
+
+    URIConst uriConst = new URIConst();
+    Assert.assertEquals("/members", uriConst.MEMBERS);
+    Assert.assertEquals("/configuration/v2/refresh/items", uriConst.REFRESH_ITEMS);
+    Assert.assertEquals("/configuration/v2/items", uriConst.ITEMS);
+  }
+
+  @Test
+  public void testURI_v3(final @Mocked ConfigCenterConfig config) {
     new Expectations() {
       {
         config.getDomainName();
@@ -35,8 +52,9 @@ public void testURI(final @Mocked ConfigCenterConfig config) {
       }
     };
 
-    Assert.assertEquals(URIConst.MEMBERS, "/v3/mytenant/configuration/members");
-    Assert.assertEquals(URIConst.REFRESH_ITEMS, "/v3/mytenant/configuration/refresh/items");
-    Assert.assertEquals(URIConst.ITEMS, "/v3/mytenant/configuration/items");
+    URIConst uriConst = new URIConst();
+    Assert.assertEquals("/v3/mytenant/configuration/members", uriConst.MEMBERS);
+    Assert.assertEquals("/v3/mytenant/configuration/refresh/items", uriConst.REFRESH_ITEMS);
+    Assert.assertEquals("/v3/mytenant/configuration/items", uriConst.ITEMS);
   }
 }
diff --git a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java
index 6e2a6f690..27f216101 100644
--- a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java
+++ b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/ConfigUtil.java
@@ -241,6 +241,16 @@ public static void installDynamicConfig() {
     }
   }
 
+  public static void destroyConfigCenterConfigurationSource() {
+    SPIServiceUtils.getAllService(ConfigCenterConfigurationSource.class).forEach(source -> {
+      try {
+        source.destroy();
+      } catch (Throwable e) {
+        LOGGER.error("Failed to destroy {}", source.getClass().getName());
+      }
+    });
+  }
+
   public static void addExtraConfig(String extraConfigName, Map<String, Object> extraConfig) {
     EXTRA_CONFIG_MAP.put(extraConfigName, extraConfig);
   }
diff --git a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/spi/ConfigCenterConfigurationSource.java b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/spi/ConfigCenterConfigurationSource.java
index 5769c556b..cc651d9dd 100644
--- a/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/spi/ConfigCenterConfigurationSource.java
+++ b/foundations/foundation-config/src/main/java/org/apache/servicecomb/config/spi/ConfigCenterConfigurationSource.java
@@ -25,4 +25,8 @@
   boolean isValidSource(Configuration localConfiguration);
 
   void init(Configuration localConfiguration);
+
+  default void destroy() {
+
+  }
 }
diff --git a/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestConfigUtil.java b/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestConfigUtil.java
index 119c6add4..c56bb7deb 100644
--- a/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestConfigUtil.java
+++ b/foundations/foundation-config/src/test/java/org/apache/servicecomb/config/TestConfigUtil.java
@@ -28,6 +28,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.commons.configuration.AbstractConfiguration;
 import org.apache.servicecomb.config.archaius.sources.ConfigModel;
@@ -48,6 +49,8 @@
 
 import mockit.Deencapsulation;
 import mockit.Expectations;
+import mockit.Mock;
+import mockit.MockUp;
 
 public class TestConfigUtil {
 
@@ -343,4 +346,26 @@ private static void setEnv(String key, String value) throws IllegalAccessExcepti
       }
     }
   }
+
+  @Test
+  public void destroyConfigCenterConfigurationSource() {
+    AtomicInteger count = new AtomicInteger();
+    ConfigCenterConfigurationSource source = new MockUp<ConfigCenterConfigurationSource>() {
+      @Mock
+      void destroy() {
+        count.incrementAndGet();
+      }
+    }.getMockInstance();
+
+    new Expectations(SPIServiceUtils.class) {
+      {
+        SPIServiceUtils.getAllService(ConfigCenterConfigurationSource.class);
+        result = Arrays.asList(source, source);
+      }
+    };
+
+    ConfigUtil.destroyConfigCenterConfigurationSource();
+
+    Assert.assertEquals(2, count.get());
+  }
 }
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsBootstrap.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsBootstrap.java
index 1a6c0d132..7f13bf17c 100644
--- a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsBootstrap.java
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsBootstrap.java
@@ -54,12 +54,14 @@ public void start(CompositeRegistry globalRegistry, EventBus eventBus) {
   }
 
   public void shutdown() {
-    executorService.shutdown();
+    if (executorService != null) {
+      executorService.shutdown();
+    }
 
     List<MetricsInitializer> initializers = new ArrayList<>(SPIServiceUtils.getSortedService(MetricsInitializer.class));
     Collections.reverse(initializers);
     initializers.forEach(initializer -> {
-      initializer.uninit();
+      initializer.destroy();
     });
   }
 
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsInitializer.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsInitializer.java
index 04f9bd4b2..b232f4815 100644
--- a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsInitializer.java
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/MetricsInitializer.java
@@ -29,7 +29,7 @@ default int getOrder() {
    */
   void init(CompositeRegistry globalRegistry, EventBus eventBus, MetricsBootstrapConfig config);
 
-  default void uninit() {
+  default void destroy() {
 
   }
 }
diff --git a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/TestMetricsBootstrap.java b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/TestMetricsBootstrap.java
index b73834b7f..50cb30c72 100644
--- a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/TestMetricsBootstrap.java
+++ b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/TestMetricsBootstrap.java
@@ -102,7 +102,7 @@ public void onEvent(PolledEvent event) {
 
   @Test
   public void shutdown(@Mocked ScheduledExecutorService scheduledExecutorService) {
-    List<MetricsInitializer> uninitList = new ArrayList<>();
+    List<MetricsInitializer> destroyList = new ArrayList<>();
     MetricsInitializer initializer1 = new MetricsInitializer() {
       @Override
       public int getOrder() {
@@ -114,8 +114,8 @@ public void init(CompositeRegistry globalRegistry, EventBus eventBus, MetricsBoo
       }
 
       @Override
-      public void uninit() {
-        uninitList.add(this);
+      public void destroy() {
+        destroyList.add(this);
       }
     };
 
@@ -130,8 +130,8 @@ public void init(CompositeRegistry globalRegistry, EventBus eventBus, MetricsBoo
       }
 
       @Override
-      public void uninit() {
-        uninitList.add(this);
+      public void destroy() {
+        destroyList.add(this);
       }
     };
 
@@ -145,6 +145,14 @@ public void uninit() {
 
     bootstrap.shutdown();
 
-    Assert.assertThat(uninitList, Matchers.contains(initializer2, initializer1));
+    Assert.assertThat(destroyList, Matchers.contains(initializer2, initializer1));
+  }
+
+  @Test
+  public void shutdown_notStart() {
+    Assert.assertNull(Deencapsulation.getField(bootstrap, "executorService"));
+
+    // should not throw exception
+    bootstrap.shutdown();
   }
 }
diff --git a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
index bcbbfd4e4..723abef24 100644
--- a/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
+++ b/handlers/handler-loadbalance/src/main/java/org/apache/servicecomb/loadbalance/LoadbalanceHandler.java
@@ -68,7 +68,10 @@
 
     @Override
     public Thread newThread(Runnable r) {
-      return new Thread(r, "retry-pool-thread-" + count.getAndIncrement());
+      Thread thread = new Thread(r, "retry-pool-thread-" + count.getAndIncrement());
+      // avoid block shutdown
+      thread.setDaemon(true);
+      return thread;
     }
   });
 
diff --git a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadbalanceHandler.java b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadbalanceHandler.java
index 5c9ccbdfa..02061e8e3 100644
--- a/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadbalanceHandler.java
+++ b/handlers/handler-loadbalance/src/test/java/org/apache/servicecomb/loadbalance/TestLoadbalanceHandler.java
@@ -22,6 +22,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 import javax.ws.rs.core.Response.Status;
 import javax.xml.ws.Holder;
@@ -471,4 +473,18 @@ public void testIsFailedResponse() {
     Assert.assertTrue(handler.isFailedResponse(Response.create(490, "", "")));
     Assert.assertTrue(handler.isFailedResponse(Response.consumerFailResp(new NullPointerException())));
   }
+
+  @Test
+  public void retryPoolDaemon() throws ExecutionException, InterruptedException {
+    ExecutorService RETRY_POOL = Deencapsulation.getField(handler, "RETRY_POOL");
+
+    Holder<Thread> nameHolder = new Holder<>();
+
+    RETRY_POOL.submit(() -> {
+      nameHolder.value = Thread.currentThread();
+    }).get();
+
+    Assert.assertThat(nameHolder.value.getName(), Matchers.startsWith("retry-pool-thread-"));
+    Assert.assertTrue(nameHolder.value.isDaemon());
+  }
 }
diff --git a/integration-tests/springmvc-tests/springmvc-tests-general-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringIntegrationTest.java b/integration-tests/springmvc-tests/springmvc-tests-general-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringIntegrationTest.java
index fbbf18137..c240f6128 100644
--- a/integration-tests/springmvc-tests/springmvc-tests-general-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringIntegrationTest.java
+++ b/integration-tests/springmvc-tests/springmvc-tests-general-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringIntegrationTest.java
@@ -35,6 +35,6 @@ public static void init() {
 
   @AfterClass
   public static void shutdown() {
-    SCBEngine.getInstance().uninit();
+    SCBEngine.getInstance().destroy();
   }
 }
diff --git a/integration-tests/springmvc-tests/springmvc-tests-general/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcIntegrationTest.java b/integration-tests/springmvc-tests/springmvc-tests-general/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcIntegrationTest.java
index 7a49b20b6..adb09ea80 100644
--- a/integration-tests/springmvc-tests/springmvc-tests-general/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcIntegrationTest.java
+++ b/integration-tests/springmvc-tests/springmvc-tests-general/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcIntegrationTest.java
@@ -32,6 +32,6 @@ public static void init() throws Exception {
 
   @AfterClass
   public static void shutdown() {
-    SCBEngine.getInstance().uninit();
+    SCBEngine.getInstance().destroy();
   }
 }
diff --git a/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringSimplifiedMappingAnnotationIntegrationTest.java b/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringSimplifiedMappingAnnotationIntegrationTest.java
index 292f9264d..bf6f70965 100644
--- a/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringSimplifiedMappingAnnotationIntegrationTest.java
+++ b/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping-with-springboot/src/test/java/org/apache/servicecomb/demo/springmvc/tests/SpringMvcSpringSimplifiedMappingAnnotationIntegrationTest.java
@@ -36,6 +36,6 @@ public static void init() {
 
   @AfterClass
   public static void shutdown() {
-    SCBEngine.getInstance().uninit();
+    SCBEngine.getInstance().destroy();
   }
 }
diff --git a/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcSimplifiedMappingAnnotationIntegrationTest.java b/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcSimplifiedMappingAnnotationIntegrationTest.java
index 35cdbd8c2..e836362aa 100644
--- a/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcSimplifiedMappingAnnotationIntegrationTest.java
+++ b/integration-tests/springmvc-tests/springmvc-tests-simplified-mapping/src/test/java/org/apache/servicecomb/demo/springmvc/tests/RawSpringMvcSimplifiedMappingAnnotationIntegrationTest.java
@@ -33,6 +33,6 @@ public static void init() throws Exception {
 
   @AfterClass
   public static void shutdown() {
-    SCBEngine.getInstance().uninit();
+    SCBEngine.getInstance().destroy();
   }
 }
diff --git a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/DefaultRegistryInitializer.java b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/DefaultRegistryInitializer.java
index 5e81ee1d8..6bd814638 100644
--- a/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/DefaultRegistryInitializer.java
+++ b/metrics/metrics-core/src/main/java/org/apache/servicecomb/metrics/core/DefaultRegistryInitializer.java
@@ -49,7 +49,7 @@ public void init(CompositeRegistry globalRegistry, EventBus eventBus, MetricsBoo
   }
 
   @Override
-  public void uninit() {
+  public void destroy() {
     DefaultMonitorRegistry.getInstance().unregister(registry);
     globalRegistry.remove(registry);
   }
diff --git a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestDefaultRegistryInitializer.java b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestDefaultRegistryInitializer.java
index 8692a80cb..4b2a1c3ff 100644
--- a/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestDefaultRegistryInitializer.java
+++ b/metrics/metrics-core/src/test/java/org/apache/servicecomb/metrics/core/TestDefaultRegistryInitializer.java
@@ -49,7 +49,7 @@ public void init() {
     Assert.assertEquals(1, registries.size());
     Assert.assertEquals(1, DefaultMonitorRegistry.getInstance().getRegisteredMonitors().size());
 
-    registryInitializer.uninit();
+    registryInitializer.destroy();
 
     Assert.assertEquals(0, registries.size());
     Assert.assertEquals(0, DefaultMonitorRegistry.getInstance().getRegisteredMonitors().size());
diff --git a/metrics/metrics-integration/metrics-prometheus/src/main/java/org/apache/servicecomb/metrics/prometheus/PrometheusPublisher.java b/metrics/metrics-integration/metrics-prometheus/src/main/java/org/apache/servicecomb/metrics/prometheus/PrometheusPublisher.java
index dd4122cfd..b1eb9c67c 100644
--- a/metrics/metrics-integration/metrics-prometheus/src/main/java/org/apache/servicecomb/metrics/prometheus/PrometheusPublisher.java
+++ b/metrics/metrics-integration/metrics-prometheus/src/main/java/org/apache/servicecomb/metrics/prometheus/PrometheusPublisher.java
@@ -114,7 +114,7 @@ protected Sample convertMeasurementToSample(Measurement measurement) {
   }
 
   @Override
-  public void uninit() {
+  public void destroy() {
     if (httpServer == null) {
       return;
     }
diff --git a/metrics/metrics-integration/metrics-prometheus/src/test/java/org/apache/servicecomb/metrics/prometheus/TestPrometheusPublisher.java b/metrics/metrics-integration/metrics-prometheus/src/test/java/org/apache/servicecomb/metrics/prometheus/TestPrometheusPublisher.java
index 1bf0766e4..c953730f4 100644
--- a/metrics/metrics-integration/metrics-prometheus/src/test/java/org/apache/servicecomb/metrics/prometheus/TestPrometheusPublisher.java
+++ b/metrics/metrics-integration/metrics-prometheus/src/test/java/org/apache/servicecomb/metrics/prometheus/TestPrometheusPublisher.java
@@ -102,6 +102,6 @@ public void collect() throws IllegalAccessException, IOException {
           "count_name{tag1=\"tag1v\",tag2=\"tag2v\",} 1.0\n", IOUtils.toString(is));
     }
 
-    publisher.uninit();
+    publisher.destroy();
   }
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services