From ad860352e7dc57bf27ac4ecbf646aa1cef47a51b Mon Sep 17 00:00:00 2001 From: arunikayadav42 Date: Tue, 6 Jan 2026 18:45:41 +0530 Subject: [PATCH 1/3] Fix: Graceful shutdown by implementing DisposableBean (#15798) --- .../context/DubboDeployApplicationListener.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java index a52246bf9088..49cd85a9ec0d 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/context/DubboDeployApplicationListener.java @@ -34,6 +34,7 @@ import java.util.concurrent.Future; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; @@ -50,7 +51,7 @@ * An ApplicationListener to control Dubbo application. */ public class DubboDeployApplicationListener - implements ApplicationListener, ApplicationContextAware, Ordered { + implements ApplicationListener, ApplicationContextAware, DisposableBean, Ordered { private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(DubboDeployApplicationListener.class); @@ -189,6 +190,12 @@ private void onContextRefreshedEvent(ContextRefreshedEvent event) { } private void onContextClosedEvent(ContextClosedEvent event) { + // remove context bind cache + DubboSpringInitializer.remove(event.getApplicationContext()); + } + + @Override + public void destroy() throws Exception { try { Object value = moduleModel.getAttribute(ModelConstants.KEEP_RUNNING_ON_SPRING_CLOSED); if (value == null) { @@ -206,8 +213,6 @@ private void onContextClosedEvent(ContextClosedEvent event) { "Unexpected error occurred when stop dubbo module: " + e.getMessage(), e); } - // remove context bind cache - DubboSpringInitializer.remove(event.getApplicationContext()); } @Override From 43ac4115ec4b92c53afb7fd53b5959ac7f306df1 Mon Sep 17 00:00:00 2001 From: arunikayadav42 Date: Tue, 6 Jan 2026 19:05:24 +0530 Subject: [PATCH 2/3] Test: Verify Dubbo graceful shutdown order (#15798) --- .../spring/context/GracefulShutdownTest.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java new file mode 100644 index 000000000000..6a536c48df47 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java @@ -0,0 +1,73 @@ +package org.apache.dubbo.config.spring.context; + +import org.apache.dubbo.config.spring.util.DubboBeanUtils; +import org.apache.dubbo.rpc.model.ModuleModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class GracefulShutdownTest { + + @Test + public void testDubboShutdownOrder() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(DubboConfig.class); + context.refresh(); + + MyTask myTask = context.getBean(MyTask.class); + + // Ensure Dubbo is running + ModuleModel moduleModel = DubboBeanUtils.getModuleModel(context); + Assertions.assertFalse(moduleModel.isDestroyed(), "Module should be running"); + + System.out.println("Closing context..."); + context.close(); + System.out.println("Context closed."); + + Assertions.assertTrue(myTask.verified.get(), "MyTask.destroy() should have been called and verified Dubbo status"); + } + + @Configuration + static class DubboConfig { + @Bean + public DubboDeployApplicationListener dubboDeployApplicationListener() { + return new DubboDeployApplicationListener(); + } + + @Bean + public MyTask myTask() { + return new MyTask(); + } + } + + static class MyTask implements DisposableBean, ApplicationContextAware { + AtomicBoolean verified = new AtomicBoolean(false); + ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void destroy() throws Exception { + System.out.println("MyTask destroying..."); + ModuleModel moduleModel = DubboBeanUtils.getModuleModel(applicationContext); + // If Dubbo is destroyed on ContextClosedEvent, this will be true. + // But we want it to be FALSE (still alive). + boolean destroyed = moduleModel.isDestroyed(); + System.out.println("Dubbo destroyed status in MyTask: " + destroyed); + + if (!destroyed) { + verified.set(true); + } + } + } +} From 675b2a4b0a5660a780c4c1db62a7b9e5739a6e35 Mon Sep 17 00:00:00 2001 From: arunikayadav42 Date: Tue, 6 Jan 2026 19:27:09 +0530 Subject: [PATCH 3/3] Style: Apply Spotless formatting --- .../spring/context/GracefulShutdownTest.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java index 6a536c48df47..09927504b585 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/GracefulShutdownTest.java @@ -1,7 +1,26 @@ +/* + * 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.config.spring.context; import org.apache.dubbo.config.spring.util.DubboBeanUtils; import org.apache.dubbo.rpc.model.ModuleModel; + +import java.util.concurrent.atomic.AtomicBoolean; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.DisposableBean; @@ -11,8 +30,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.concurrent.atomic.AtomicBoolean; - public class GracefulShutdownTest { @Test @@ -22,7 +39,7 @@ public void testDubboShutdownOrder() { context.refresh(); MyTask myTask = context.getBean(MyTask.class); - + // Ensure Dubbo is running ModuleModel moduleModel = DubboBeanUtils.getModuleModel(context); Assertions.assertFalse(moduleModel.isDestroyed(), "Module should be running"); @@ -31,7 +48,8 @@ public void testDubboShutdownOrder() { context.close(); System.out.println("Context closed."); - Assertions.assertTrue(myTask.verified.get(), "MyTask.destroy() should have been called and verified Dubbo status"); + Assertions.assertTrue( + myTask.verified.get(), "MyTask.destroy() should have been called and verified Dubbo status"); } @Configuration @@ -64,7 +82,7 @@ public void destroy() throws Exception { // But we want it to be FALSE (still alive). boolean destroyed = moduleModel.isDestroyed(); System.out.println("Dubbo destroyed status in MyTask: " + destroyed); - + if (!destroyed) { verified.set(true); }