Ensure Dubbo can shutdown correctly when running both under tomcat and programmably.
diff --git a/all/pom.xml b/all/pom.xml
index 09bfe85..edc0c4a 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -321,6 +321,13 @@
         </dependency>
         <dependency>
             <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-bootstrap</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
             <artifactId>hessian-lite</artifactId>
             <version>3.2.2</version>
             <scope>compile</scope>
@@ -418,6 +425,7 @@
                                     <include>com.alibaba:dubbo-serialization-fst</include>
                                     <include>com.alibaba:dubbo-serialization-kryo</include>
                                     <include>com.alibaba:dubbo-serialization-jdk</include>
+                                    <include>com.alibaba:dubbo-bootstrap</include>
                                 </includes>
                             </artifactSet>
                             <transformers>
diff --git a/dubbo-bootstrap/pom.xml b/dubbo-bootstrap/pom.xml
new file mode 100644
index 0000000..e4320f0
--- /dev/null
+++ b/dubbo-bootstrap/pom.xml
@@ -0,0 +1,47 @@
+<!--
+  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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>dubbo-parent</artifactId>
+        <groupId>com.alibaba</groupId>
+        <version>2.6.3-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-bootstrap</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-config-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>dubbo-registry-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java
new file mode 100644
index 0000000..8694e48
--- /dev/null
+++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java
@@ -0,0 +1,133 @@
+/*
+ * 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.dubbo.bootstrap;
+
+import com.alibaba.dubbo.common.extension.ExtensionLoader;
+import com.alibaba.dubbo.common.logger.Logger;
+import com.alibaba.dubbo.common.logger.LoggerFactory;
+import com.alibaba.dubbo.config.ServiceConfig;
+import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;
+import com.alibaba.dubbo.rpc.Protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A bootstrap class to easily start and stop Dubbo via programmatic API.
+ * The bootstrap class will be responsible to cleanup the resources during stop.
+ */
+public class DubboBootstrap {
+
+    private static final Logger logger = LoggerFactory.getLogger(DubboBootstrap.class);
+
+    /**
+     * The list of ServiceConfig
+     */
+    private List<ServiceConfig> serviceConfigList;
+
+    /**
+     * Has it already been destroyed or not?
+     */
+    private final AtomicBoolean destroyed;
+
+    /**
+     * The shutdown hook used when Dubbo is running under embedded environment
+     */
+    private Thread shutdownHook;
+
+    public DubboBootstrap() {
+        this.serviceConfigList = new ArrayList<ServiceConfig>();
+        this.destroyed = new AtomicBoolean(false);
+        this.shutdownHook = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                if (logger.isInfoEnabled()) {
+                    logger.info("Run shutdown hook now.");
+                }
+                destroy();
+            }
+        }, "DubboShutdownHook");
+    }
+
+    /**
+     * Register service config to bootstrap, which will be called during {@link DubboBootstrap#stop()}
+     * @param serviceConfig the service
+     * @return the bootstrap instance
+     */
+    public DubboBootstrap regsiterServiceConfig(ServiceConfig serviceConfig) {
+        serviceConfigList.add(serviceConfig);
+        return this;
+    }
+
+    public void start() {
+        registerShutdownHook();
+        for (ServiceConfig serviceConfig: serviceConfigList) {
+            serviceConfig.export();
+        }
+    }
+
+    public void stop() {
+        for (ServiceConfig serviceConfig: serviceConfigList) {
+            serviceConfig.unexport();
+        }
+        destroy();
+        removeShutdownHook();
+    }
+
+    /**
+     * Register the shutdown hook
+     */
+    public void registerShutdownHook() {
+        Runtime.getRuntime().addShutdownHook(shutdownHook);
+    }
+
+    /**
+     * Remove this shutdown hook
+     */
+    public void removeShutdownHook() {
+        try {
+            Runtime.getRuntime().removeShutdownHook(shutdownHook);
+        }
+        catch (IllegalStateException ex) {
+            // ignore - VM is already shutting down
+        }
+    }
+
+    /**
+     * Destroy all the resources, including registries and protocols.
+     */
+    private void destroy() {
+        if (!destroyed.compareAndSet(false, true)) {
+            return;
+        }
+        // destroy all the registries
+        AbstractRegistryFactory.destroyAll();
+        // destroy all the protocols
+        ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
+        for (String protocolName : loader.getLoadedExtensions()) {
+            try {
+                Protocol protocol = loader.getLoadedExtension(protocolName);
+                if (protocol != null) {
+                    protocol.destroy();
+                }
+            } catch (Throwable t) {
+                logger.warn(t.getMessage(), t);
+            }
+        }
+    }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java
index 73fc07d..8d8d182 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java
@@ -73,18 +73,6 @@
         legacyProperties.put("dubbo.service.url", "dubbo.service.address");

     }

 

-    static {

-        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

-            @Override

-            public void run() {

-                if (logger.isInfoEnabled()) {

-                    logger.info("Run shutdown hook now.");

-                }

-                ProtocolConfig.destroyAll();

-            }

-        }, "DubboShutdownHook"));

-    }

-

     protected String id;

 

     private static String convertLegacyValue(String key, String value) {

diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java
index 0e7a524..b15129b 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java
@@ -21,7 +21,6 @@
 import com.alibaba.dubbo.common.status.StatusChecker;

 import com.alibaba.dubbo.common.threadpool.ThreadPool;

 import com.alibaba.dubbo.config.support.Parameter;

-import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;

 import com.alibaba.dubbo.remoting.Codec;

 import com.alibaba.dubbo.remoting.Dispatcher;

 import com.alibaba.dubbo.remoting.Transporter;

@@ -30,7 +29,6 @@
 import com.alibaba.dubbo.rpc.Protocol;

 

 import java.util.Map;

-import java.util.concurrent.atomic.AtomicBoolean;

 

 /**

  * ProtocolConfig

@@ -135,8 +133,6 @@
     // if it's default

     private Boolean isDefault;

 

-    private static final AtomicBoolean destroyed = new AtomicBoolean(false);

-

     public ProtocolConfig() {

     }

 

@@ -149,27 +145,6 @@
         setPort(port);

     }

 

-    // TODO: 2017/8/30 to move this method somewhere else

-    public static void destroyAll() {

-        if (!destroyed.compareAndSet(false, true)) {

-            return;

-        }

-

-        AbstractRegistryFactory.destroyAll();

-

-        ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);

-        for (String protocolName : loader.getLoadedExtensions()) {

-            try {

-                Protocol protocol = loader.getLoadedExtension(protocolName);

-                if (protocol != null) {

-                    protocol.destroy();

-                }

-            } catch (Throwable t) {

-                logger.warn(t.getMessage(), t);

-            }

-        }

-    }

-

     @Parameter(excluded = true)

     public String getName() {

         return name;

diff --git a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java
index ed9db80..e5684f3 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/RegistryConfig.java
@@ -18,7 +18,6 @@
 

 import com.alibaba.dubbo.common.Constants;

 import com.alibaba.dubbo.config.support.Parameter;

-import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;

 

 import java.util.Map;

 

diff --git a/dubbo-config/dubbo-config-spring/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml
index c7a6660..1ba2068 100644
--- a/dubbo-config/dubbo-config-spring/pom.xml
+++ b/dubbo-config/dubbo-config-spring/pom.xml
@@ -36,6 +36,11 @@
             <version>${project.parent.version}</version>

         </dependency>

         <dependency>

+            <groupId>com.alibaba</groupId>

+            <artifactId>dubbo-bootstrap</artifactId>

+            <version>${project.parent.version}</version>

+        </dependency>

+        <dependency>

             <groupId>org.springframework</groupId>

             <artifactId>spring-beans</artifactId>

         </dependency>

@@ -44,6 +49,11 @@
             <artifactId>spring-context</artifactId>

         </dependency>

         <dependency>

+            <groupId>javax.servlet</groupId>

+            <artifactId>javax.servlet-api</artifactId>

+            <scope>provided</scope>

+        </dependency>

+        <dependency>

             <groupId>com.alibaba</groupId>

             <artifactId>dubbo-registry-default</artifactId>

             <version>${project.parent.version}</version>

diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java
new file mode 100644
index 0000000..43ee49d
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.alibaba.dubbo.config.spring.initializer;
+
+import org.apache.dubbo.bootstrap.DubboBootstrap;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+/**
+ * An application listener that listens the ContextClosedEvent.
+ * Upon the event, this listener will do the necessary clean up to avoid memory leak.
+ */
+public class DubboApplicationListener implements ApplicationListener<ApplicationEvent> {
+
+    private DubboBootstrap dubboBootstrap;
+
+    public DubboApplicationListener() {
+        dubboBootstrap = new DubboBootstrap();
+    }
+
+    @Override
+    public void onApplicationEvent(ApplicationEvent applicationEvent) {
+        if (applicationEvent instanceof ContextRefreshedEvent) {
+            dubboBootstrap.start();
+        } else if (applicationEvent instanceof ContextClosedEvent) {
+            dubboBootstrap.stop();
+        }
+    }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboWebApplicationInitializer.java b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboWebApplicationInitializer.java
new file mode 100644
index 0000000..0956d88
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboWebApplicationInitializer.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.alibaba.dubbo.config.spring.initializer;
+
+import org.springframework.web.context.AbstractContextLoaderInitializer;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.XmlWebApplicationContext;
+
+
+public class DubboWebApplicationInitializer extends AbstractContextLoaderInitializer {
+
+    @Override
+    protected WebApplicationContext createRootApplicationContext() {
+        // TODO need to verify under spring-boot
+        XmlWebApplicationContext webApplicationContext = new XmlWebApplicationContext();
+        webApplicationContext.addApplicationListener(new DubboApplicationListener());
+        return webApplicationContext;
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java
index ced0efa..010e35f 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/com/alibaba/dubbo/registry/support/FailbackRegistry.java
@@ -29,6 +29,7 @@
 import java.util.Set;

 import java.util.concurrent.ConcurrentHashMap;

 import java.util.concurrent.ConcurrentMap;

+import java.util.concurrent.ExecutorService;

 import java.util.concurrent.Executors;

 import java.util.concurrent.Future;

 import java.util.concurrent.ScheduledExecutorService;

@@ -57,9 +58,14 @@
 

     private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();

 

+    /**

+     * The time in milliseconds the retryExecutor will wait

+     */

+    private final int retryPeriod;

+

     public FailbackRegistry(URL url) {

         super(url);

-        int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);

+        this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);

         this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {

             @Override

             public void run() {

@@ -440,6 +446,32 @@
         } catch (Throwable t) {

             logger.warn(t.getMessage(), t);

         }

+        shutdownExecutorService(retryExecutor, retryPeriod);

+    }

+

+    /**

+     * Use the shutdown pattern from:

+     *  https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html

+     * @param pool the ExecutorService to be shutdown

+     * @param timeoutInMillis the timeout before terminication

+     */

+    protected void shutdownExecutorService(ExecutorService pool, int timeoutInMillis) {

+        // Disable new tasks from being submitted

+        pool.shutdown();

+        try {

+            // Wait a while for existing tasks to terminate

+            if (!pool.awaitTermination(timeoutInMillis, TimeUnit.MILLISECONDS)) {

+                pool.shutdownNow(); // Cancel currently executing tasks

+                // Wait a while for tasks to respond to being cancelled

+                if (!pool.awaitTermination(timeoutInMillis, TimeUnit.MILLISECONDS))

+                    logger.warn("ExecutorService did not terminate: " + pool.toString());

+            }

+        } catch (InterruptedException ie) {

+            // (Re-)Cancel if current thread also interrupted

+            pool.shutdownNow();

+            // Preserve interrupt status

+            Thread.currentThread().interrupt();

+        }

     }

 

     // ==== Template method ====

diff --git a/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java b/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java
index 9d5ab70..8f6c0c8 100644
--- a/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java
+++ b/dubbo-registry/dubbo-registry-default/src/main/java/com/alibaba/dubbo/registry/dubbo/DubboRegistry.java
@@ -47,7 +47,7 @@
     private static final int RECONNECT_PERIOD_DEFAULT = 3 * 1000;

 

     // Scheduled executor service

-    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryReconnectTimer", true));

+    private final ScheduledExecutorService reconnectTimer = Executors.newScheduledThreadPool(1, new NamedThreadFactory("DubboRegistryReconnectTimer", true));

 

     // Reconnection timer, regular check connection is available. If unavailable, unlimited reconnection.

     private final ScheduledFuture<?> reconnectFuture;

@@ -59,13 +59,18 @@
 

     private final RegistryService registryService;

 

+    /**

+     * The time in milliseconds the reconnectTimer will wait

+     */

+    private final int reconnectPeriod;

+

     public DubboRegistry(Invoker<RegistryService> registryInvoker, RegistryService registryService) {

         super(registryInvoker.getUrl());

         this.registryInvoker = registryInvoker;

         this.registryService = registryService;

         // Start reconnection timer

-        int reconnectPeriod = registryInvoker.getUrl().getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, RECONNECT_PERIOD_DEFAULT);

-        reconnectFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

+        this.reconnectPeriod = registryInvoker.getUrl().getParameter(Constants.REGISTRY_RECONNECT_PERIOD_KEY, RECONNECT_PERIOD_DEFAULT);

+        reconnectFuture = reconnectTimer.scheduleWithFixedDelay(new Runnable() {

             @Override

             public void run() {

                 // Check and connect to the registry

@@ -127,6 +132,7 @@
             logger.warn("Failed to cancel reconnect timer", t);

         }

         registryInvoker.destroy();

+        shutdownExecutorService(reconnectTimer, reconnectPeriod);

     }

 

     @Override

diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java
index aa499fc..5f836ff 100644
--- a/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java
+++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/com/alibaba/dubbo/registry/multicast/MulticastRegistry.java
@@ -314,6 +314,7 @@
         } catch (Throwable t) {

             logger.warn(t.getMessage(), t);

         }

+        shutdownExecutorService(cleanExecutor, cleanPeriod);

     }

 

     protected void registered(URL url) {

diff --git a/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java b/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java
index ea4b22c..0f75cf6 100644
--- a/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java
+++ b/dubbo-registry/dubbo-registry-redis/src/main/java/com/alibaba/dubbo/registry/redis/RedisRegistry.java
@@ -263,6 +263,7 @@
                 logger.warn("Failed to destroy the redis registry client. registry: " + entry.getKey() + ", cause: " + t.getMessage(), t);

             }

         }

+        shutdownExecutorService(expireExecutor, expirePeriod);

     }

 

     @Override

diff --git a/pom.xml b/pom.xml
index 856eca6..9af7c4c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,6 +133,7 @@
         <module>dubbo-demo</module>
         <module>dubbo-plugin</module>
         <module>dubbo-serialization</module>
+        <module>dubbo-bootstrap</module>
         <module>dependencies-bom</module>
         <module>bom</module>
         <module>all</module>