From 69f9ee8aa6b307482b23141b9b5c7e4a1ae8152a Mon Sep 17 00:00:00 2001 From: vividctrlalt Date: Wed, 19 Aug 2026 13:11:30 +0800 Subject: [PATCH] fix(backup): avoid hang when closing unbound channel --- .../org/tron/common/backup/BackupManager.java | 8 ++++++++ .../tron/common/backup/socket/BackupServer.java | 17 +++++++++++++++-- .../tron/common/backup/BackupServerTest.java | 8 ++++++-- .../org/tron/common/utils/PublicMethod.java | 4 ++-- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/framework/src/main/java/org/tron/common/backup/BackupManager.java b/framework/src/main/java/org/tron/common/backup/BackupManager.java index a870c183a8d..832399240ee 100644 --- a/framework/src/main/java/org/tron/common/backup/BackupManager.java +++ b/framework/src/main/java/org/tron/common/backup/BackupManager.java @@ -172,6 +172,14 @@ public void handleEvent(UdpEvent udpEvent) { } } + /** + * Stop the keep-alive scheduler without waiting so the UDP channel can be closed + * before a subsequent write() hits a closed channel. + */ + public void stopScheduler() { + executorService.shutdown(); + } + public void stop() { ExecutorServiceManager.shutdownAndAwaitTermination(executorService, esName); if (dnsExecutorService != null) { diff --git a/framework/src/main/java/org/tron/common/backup/socket/BackupServer.java b/framework/src/main/java/org/tron/common/backup/socket/BackupServer.java index 67739ac50d2..d685f5dd3e3 100644 --- a/framework/src/main/java/org/tron/common/backup/socket/BackupServer.java +++ b/framework/src/main/java/org/tron/common/backup/socket/BackupServer.java @@ -27,7 +27,8 @@ public class BackupServer implements AutoCloseable { private BackupManager backupManager; - private Channel channel; + // volatile: Dekker pairing with shutdown so close() observes bind and start() observes close. + private volatile Channel channel; private volatile boolean shutdown = false; @@ -52,6 +53,11 @@ public void initServer() { } } + public boolean isBound() { + Channel ch = channel; + return ch != null && ch.isActive(); + } + private void start() throws Exception { NioEventLoopGroup group = new NioEventLoopGroup(1); try { @@ -77,6 +83,11 @@ public void initChannel(NioDatagramChannel ch) logger.info("Backup server started, bind port {}", port); + // close() may have raced bind (Dekker). Close now so closeFuture.sync() cannot hang. + if (shutdown) { + channel.close(); + } + channel.closeFuture().sync(); if (shutdown) { logger.info("Shutdown backup BackupServer"); @@ -95,7 +106,8 @@ public void initChannel(NioDatagramChannel ch) public void close() { logger.info("Closing backup server..."); shutdown = true; - backupManager.stop(); + // Stop keep-alive first (no wait) so it cannot write() after the channel is closed. + backupManager.stopScheduler(); if (channel != null) { try { channel.close().await(10, TimeUnit.SECONDS); @@ -103,6 +115,7 @@ public void close() { logger.warn("Closing backup server failed.", e); } } + backupManager.stop(); ExecutorServiceManager.shutdownAndAwaitTermination(executor, name); logger.info("Backup server closed."); } diff --git a/framework/src/test/java/org/tron/common/backup/BackupServerTest.java b/framework/src/test/java/org/tron/common/backup/BackupServerTest.java index 50778970d87..837c2a67b9b 100644 --- a/framework/src/test/java/org/tron/common/backup/BackupServerTest.java +++ b/framework/src/test/java/org/tron/common/backup/BackupServerTest.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -46,7 +47,10 @@ public void tearDown() { @Test(timeout = 60_000) public void test() throws InterruptedException { backupServer.initServer(); - // wait for the server to start so channel is assigned before close() is called - Thread.sleep(1000); + long deadline = System.currentTimeMillis() + 15_000L; + while (!backupServer.isBound() && System.currentTimeMillis() < deadline) { + Thread.sleep(50); + } + Assert.assertTrue("BackupServer UDP channel did not bind", backupServer.isBound()); } } diff --git a/framework/src/test/java/org/tron/common/utils/PublicMethod.java b/framework/src/test/java/org/tron/common/utils/PublicMethod.java index 90a2aae3f76..915255bde30 100644 --- a/framework/src/test/java/org/tron/common/utils/PublicMethod.java +++ b/framework/src/test/java/org/tron/common/utils/PublicMethod.java @@ -343,8 +343,8 @@ public static int chooseRandomPort(int min, int max) { } private static boolean checkPortAvailable(int port) throws IOException { - try (java.net.ServerSocket ss = new java.net.ServerSocket(port)) { - ss.setReuseAddress(true); + try (java.net.ServerSocket ss = new java.net.ServerSocket(port); + java.net.DatagramSocket ds = new java.net.DatagramSocket(port)) { return true; } catch (IOException e) { return false;