Skip to content

Commit 1b553fd

Browse files
ralf0131chickenlj
authored andcommitted
Merge pull request #1820, improve graceful shutdown.
1 parent fd7bab3 commit 1b553fd

File tree

9 files changed

+197
-51
lines changed

9 files changed

+197
-51
lines changed

dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,66 +16,65 @@
1616
*/
1717
package org.apache.dubbo.bootstrap;
1818

19-
import com.alibaba.dubbo.common.extension.ExtensionLoader;
20-
import com.alibaba.dubbo.common.logger.Logger;
21-
import com.alibaba.dubbo.common.logger.LoggerFactory;
19+
import com.alibaba.dubbo.config.DubboShutdownHook;
2220
import com.alibaba.dubbo.config.ServiceConfig;
23-
import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;
24-
import com.alibaba.dubbo.rpc.Protocol;
2521

2622
import java.util.ArrayList;
2723
import java.util.List;
28-
import java.util.concurrent.atomic.AtomicBoolean;
2924

3025
/**
3126
* A bootstrap class to easily start and stop Dubbo via programmatic API.
3227
* The bootstrap class will be responsible to cleanup the resources during stop.
3328
*/
3429
public class DubboBootstrap {
3530

36-
private static final Logger logger = LoggerFactory.getLogger(DubboBootstrap.class);
37-
3831
/**
3932
* The list of ServiceConfig
4033
*/
4134
private List<ServiceConfig> serviceConfigList;
4235

4336
/**
44-
* Has it already been destroyed or not?
37+
* Whether register the shutdown hook during start?
4538
*/
46-
private final AtomicBoolean destroyed;
39+
private final boolean registerShutdownHookOnStart;
4740

4841
/**
4942
* The shutdown hook used when Dubbo is running under embedded environment
5043
*/
51-
private Thread shutdownHook;
44+
private DubboShutdownHook shutdownHook;
5245

5346
public DubboBootstrap() {
47+
this(true, DubboShutdownHook.getDubboShutdownHook());
48+
}
49+
50+
public DubboBootstrap(boolean registerShutdownHookOnStart) {
51+
this(registerShutdownHookOnStart, DubboShutdownHook.getDubboShutdownHook());
52+
}
53+
54+
public DubboBootstrap(boolean registerShutdownHookOnStart, DubboShutdownHook shutdownHook) {
5455
this.serviceConfigList = new ArrayList<ServiceConfig>();
55-
this.destroyed = new AtomicBoolean(false);
56-
this.shutdownHook = new Thread(new Runnable() {
57-
@Override
58-
public void run() {
59-
if (logger.isInfoEnabled()) {
60-
logger.info("Run shutdown hook now.");
61-
}
62-
destroy();
63-
}
64-
}, "DubboShutdownHook");
56+
this.shutdownHook = shutdownHook;
57+
this.registerShutdownHookOnStart = registerShutdownHookOnStart;
6558
}
6659

6760
/**
6861
* Register service config to bootstrap, which will be called during {@link DubboBootstrap#stop()}
6962
* @param serviceConfig the service
7063
* @return the bootstrap instance
7164
*/
72-
public DubboBootstrap regsiterServiceConfig(ServiceConfig serviceConfig) {
65+
public DubboBootstrap registerServiceConfig(ServiceConfig serviceConfig) {
7366
serviceConfigList.add(serviceConfig);
7467
return this;
7568
}
7669

7770
public void start() {
78-
registerShutdownHook();
71+
if (registerShutdownHookOnStart) {
72+
registerShutdownHook();
73+
} else {
74+
// DubboShutdown hook has been registered in AbstractConfig,
75+
// we need to remove it explicitly
76+
removeShutdownHook();
77+
}
7978
for (ServiceConfig serviceConfig: serviceConfigList) {
8079
serviceConfig.export();
8180
}
@@ -85,8 +84,10 @@ public void stop() {
8584
for (ServiceConfig serviceConfig: serviceConfigList) {
8685
serviceConfig.unexport();
8786
}
88-
destroy();
89-
removeShutdownHook();
87+
shutdownHook.destroyAll();
88+
if (registerShutdownHookOnStart) {
89+
removeShutdownHook();
90+
}
9091
}
9192

9293
/**
@@ -107,27 +108,4 @@ public void removeShutdownHook() {
107108
// ignore - VM is already shutting down
108109
}
109110
}
110-
111-
/**
112-
* Destroy all the resources, including registries and protocols.
113-
*/
114-
private void destroy() {
115-
if (!destroyed.compareAndSet(false, true)) {
116-
return;
117-
}
118-
// destroy all the registries
119-
AbstractRegistryFactory.destroyAll();
120-
// destroy all the protocols
121-
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
122-
for (String protocolName : loader.getLoadedExtensions()) {
123-
try {
124-
Protocol protocol = loader.getLoadedExtension(protocolName);
125-
if (protocol != null) {
126-
protocol.destroy();
127-
}
128-
} catch (Throwable t) {
129-
logger.warn(t.getMessage(), t);
130-
}
131-
}
132-
}
133111
}

dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/AbstractConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ public abstract class AbstractConfig implements Serializable {
7171
legacyProperties.put("dubbo.consumer.retries", "dubbo.service.max.retry.providers");
7272
legacyProperties.put("dubbo.consumer.check", "dubbo.service.allow.no.provider");
7373
legacyProperties.put("dubbo.service.url", "dubbo.service.address");
74+
75+
// this is only for compatibility
76+
Runtime.getRuntime().addShutdownHook(DubboShutdownHook.getDubboShutdownHook());
7477
}
7578

7679
protected String id;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.alibaba.dubbo.config;
18+
19+
import com.alibaba.dubbo.common.extension.ExtensionLoader;
20+
import com.alibaba.dubbo.common.logger.Logger;
21+
import com.alibaba.dubbo.common.logger.LoggerFactory;
22+
import com.alibaba.dubbo.registry.support.AbstractRegistryFactory;
23+
import com.alibaba.dubbo.rpc.Protocol;
24+
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
27+
/**
28+
* The shutdown hook thread to do the clean up stuff.
29+
* This is a singleton in order to ensure there is only one shutdown hook registered.
30+
* Because {@link ApplicationShutdownHooks} use {@link java.util.IdentityHashMap}
31+
* to store the shutdown hooks.
32+
*/
33+
public class DubboShutdownHook extends Thread {
34+
35+
private static final Logger logger = LoggerFactory.getLogger(DubboShutdownHook.class);
36+
37+
private static final DubboShutdownHook dubboShutdownHook = new DubboShutdownHook("DubboShutdownHook");
38+
39+
public static DubboShutdownHook getDubboShutdownHook() {
40+
return dubboShutdownHook;
41+
}
42+
43+
/**
44+
* Has it already been destroyed or not?
45+
*/
46+
private final AtomicBoolean destroyed;
47+
48+
private DubboShutdownHook(String name) {
49+
super(name);
50+
this.destroyed = new AtomicBoolean(false);
51+
}
52+
53+
@Override
54+
public void run() {
55+
if (logger.isInfoEnabled()) {
56+
logger.info("Run shutdown hook now.");
57+
}
58+
destroyAll();
59+
}
60+
61+
/**
62+
* Destroy all the resources, including registries and protocols.
63+
*/
64+
public void destroyAll() {
65+
if (!destroyed.compareAndSet(false, true)) {
66+
return;
67+
}
68+
// destroy all the registries
69+
AbstractRegistryFactory.destroyAll();
70+
// destroy all the protocols
71+
ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
72+
for (String protocolName : loader.getLoadedExtensions()) {
73+
try {
74+
Protocol protocol = loader.getLoadedExtension(protocolName);
75+
if (protocol != null) {
76+
protocol.destroy();
77+
}
78+
} catch (Throwable t) {
79+
logger.warn(t.getMessage(), t);
80+
}
81+
}
82+
}
83+
84+
85+
}

dubbo-config/dubbo-config-api/src/main/java/com/alibaba/dubbo/config/ProtocolConfig.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,12 @@ public void destroy() {
460460
}
461461
}
462462

463+
/**
464+
* Just for compatibility.
465+
* It should be deleted in the next major version, say 2.7.x.
466+
*/
467+
@Deprecated
468+
public static void destroyAll() {
469+
DubboShutdownHook.getDubboShutdownHook().destroyAll();
470+
}
463471
}

dubbo-config/dubbo-config-spring/src/main/java/com/alibaba/dubbo/config/spring/initializer/DubboApplicationListener.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ public class DubboApplicationListener implements ApplicationListener<Application
3131
private DubboBootstrap dubboBootstrap;
3232

3333
public DubboApplicationListener() {
34-
dubboBootstrap = new DubboBootstrap();
34+
dubboBootstrap = new DubboBootstrap(false);
35+
}
36+
37+
public DubboApplicationListener(DubboBootstrap dubboBootstrap) {
38+
this.dubboBootstrap = dubboBootstrap;
3539
}
3640

3741
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.alibaba.dubbo.config.spring.initializer;
18+
19+
import com.alibaba.dubbo.config.DubboShutdownHook;
20+
import org.apache.dubbo.bootstrap.DubboBootstrap;
21+
import org.junit.Test;
22+
import org.mockito.Mockito;
23+
import org.springframework.context.support.ClassPathXmlApplicationContext;
24+
25+
public class DubboApplicationListenerTest {
26+
27+
@Test
28+
public void testTwoShutdownHook() {
29+
DubboShutdownHook spyHook = Mockito.spy(DubboShutdownHook.getDubboShutdownHook());
30+
ClassPathXmlApplicationContext applicationContext = getApplicationContext(spyHook, true);
31+
applicationContext.refresh();
32+
applicationContext.close();
33+
// shutdown hook can't be verified, because it will executed after main thread has finished.
34+
// so we can only verify it by manually run it.
35+
try {
36+
spyHook.start();
37+
spyHook.join();
38+
} catch (InterruptedException e) {
39+
e.printStackTrace();
40+
}
41+
Mockito.verify(spyHook, Mockito.times(2)).destroyAll();
42+
}
43+
44+
@Test
45+
public void testOneShutdownHook() {
46+
DubboShutdownHook spyHook = Mockito.spy(DubboShutdownHook.getDubboShutdownHook());
47+
ClassPathXmlApplicationContext applicationContext = getApplicationContext(spyHook, false);
48+
applicationContext.refresh();
49+
applicationContext.close();
50+
Mockito.verify(spyHook, Mockito.times(1)).destroyAll();
51+
}
52+
53+
private ClassPathXmlApplicationContext getApplicationContext(DubboShutdownHook hook, boolean registerHook) {
54+
DubboBootstrap bootstrap = new DubboBootstrap(registerHook, hook);
55+
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
56+
applicationContext.addApplicationListener(new DubboApplicationListener(bootstrap));
57+
return applicationContext;
58+
}
59+
}

dubbo-container/dubbo-container-api/src/main/java/com/alibaba/dubbo/container/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static void main(String[] args) {
6161
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
6262

6363
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
64-
Runtime.getRuntime().addShutdownHook(new Thread() {
64+
Runtime.getRuntime().addShutdownHook(new Thread("dubbo-container-shutdown-hook") {
6565
@Override
6666
public void run() {
6767
for (Container container : containers) {

dubbo-container/dubbo-container-spring/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,10 @@
3939
<groupId>org.springframework</groupId>
4040
<artifactId>spring-context</artifactId>
4141
</dependency>
42+
<dependency>
43+
<groupId>com.alibaba</groupId>
44+
<artifactId>dubbo-config-spring</artifactId>
45+
<version>${project.parent.version}</version>
46+
</dependency>
4247
</dependencies>
4348
</project>

dubbo-container/dubbo-container-spring/src/main/java/com/alibaba/dubbo/container/spring/SpringContainer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.alibaba.dubbo.common.logger.Logger;
2020
import com.alibaba.dubbo.common.logger.LoggerFactory;
2121
import com.alibaba.dubbo.common.utils.ConfigUtils;
22+
import com.alibaba.dubbo.config.spring.initializer.DubboApplicationListener;
2223
import com.alibaba.dubbo.container.Container;
2324

2425
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -43,7 +44,10 @@ public void start() {
4344
if (configPath == null || configPath.length() == 0) {
4445
configPath = DEFAULT_SPRING_CONFIG;
4546
}
46-
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
47+
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
48+
context.addApplicationListener(new DubboApplicationListener());
49+
context.registerShutdownHook();
50+
context.refresh();
4751
context.start();
4852
}
4953

0 commit comments

Comments
 (0)