Skip to content

Commit bc2a3a5

Browse files
committed
fix: GH-0000 exit on SIGTERM before context attach
1 parent f24bc0c commit bc2a3a5

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

tools/server-boot/src/main/java/org/eclipse/rdf4j/tools/serverboot/SignalShutdownHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,26 @@ private void handleSignal(String signalName) {
8080
context.close();
8181
}
8282
logger.info("Application context closed after SIG{}, exit status {}", signalName, exitCode);
83-
System.exit(exitCode);
83+
exitJvm(exitCode, signalName);
8484
} catch (Throwable e) {
8585
logger.warn("Error while shutting down after SIG{}", signalName, e);
8686
}
8787
} else {
8888
logger.warn("SIG{} received before application context became available; shutting down immediately.",
8989
signalName);
90+
exitJvm(0, signalName);
9091
}
9192

9293
}
9394

95+
private static void exitJvm(int exitCode, String signalName) {
96+
try {
97+
System.exit(exitCode);
98+
} catch (SecurityException e) {
99+
logger.error("System.exit({}) blocked by security manager after SIG{}", exitCode, signalName, e);
100+
}
101+
}
102+
94103
private static void startDelayedSystemExitThread(String signalName) {
95104
// Start a thread that will forcibly exit the JVM after a delay, in case spring-boot hangs during shutdown
96105
Thread thread = new Thread(() -> {

tools/server-boot/src/test/java/org/eclipse/rdf4j/tools/serverboot/ServerBootSignalIT.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.concurrent.CountDownLatch;
3030
import java.util.concurrent.ExecutorService;
3131
import java.util.concurrent.Executors;
32+
import java.util.concurrent.TimeUnit;
3233
import java.util.concurrent.atomic.AtomicBoolean;
3334

3435
import org.eclipse.rdf4j.model.IRI;
@@ -95,6 +96,11 @@ void gracefullyStopsOnSigterm() throws Exception {
9596
assertGracefulShutdown("TERM");
9697
}
9798

99+
@Test
100+
void exitsOnSigtermBeforeContextAttachment() throws Exception {
101+
assertShutdownBeforeContextAttachment("TERM");
102+
}
103+
98104
private void assertGracefulShutdownWithSigintFallback() throws Exception {
99105
assertGracefulShutdown("INT", true);
100106
}
@@ -103,6 +109,52 @@ private void assertGracefulShutdown(String signalName) throws Exception {
103109
assertGracefulShutdown(signalName, false);
104110
}
105111

112+
private void assertShutdownBeforeContextAttachment(String signalName) throws Exception {
113+
Path projectRoot = Path.of("").toAbsolutePath();
114+
String javaBin = Path.of(System.getProperty("java.home"), "bin", "java").toString();
115+
int serverPort = findFreePort();
116+
int managementPort = findFreePort();
117+
118+
Path targetDir = projectRoot.resolve("target");
119+
Path jarPath = Files.list(targetDir)
120+
.sorted(Comparator.comparing(Path::toString))
121+
.filter(p -> p.toString().endsWith(".jar"))
122+
.filter(p -> !p.toString().endsWith("-sources.jar"))
123+
.filter(p -> !p.toString().endsWith("-javadoc.jar"))
124+
.findFirst()
125+
.orElseThrow(() -> new IllegalStateException("Could not find executable JAR in " + targetDir));
126+
127+
ProcessBuilder processBuilder = new ProcessBuilder(javaBin, "-jar", jarPath.toString(),
128+
"--server.port=" + serverPort,
129+
"--management.server.port=" + managementPort);
130+
processBuilder.directory(projectRoot.toFile());
131+
processBuilder.redirectErrorStream(true);
132+
133+
Process process = processBuilder.start();
134+
cleanupActions.add(() -> process.destroyForcibly());
135+
136+
CountDownLatch started = new CountDownLatch(1);
137+
StringBuilder outputBuffer = new StringBuilder();
138+
startStreamGobbler(process, started, outputBuffer);
139+
140+
boolean registeredHandler = waitForOutputContains(outputBuffer,
141+
"Registered SIGTERM handler for graceful shutdown.", 30, SECONDS);
142+
assertThat(registeredHandler)
143+
.as(() -> "Did not observe SIGTERM handler registration before sending signal. Output:\\n"
144+
+ outputBuffer)
145+
.isTrue();
146+
147+
sendSignal(process.pid(), signalName);
148+
boolean exited = process.waitFor(30, SECONDS);
149+
assertThat(exited)
150+
.as(() -> "Process did not exit after SIG" + signalName
151+
+ " sent before context attachment. Output:\\n" + outputBuffer)
152+
.isTrue();
153+
assertThat(process.exitValue())
154+
.as(() -> "Process exit value after startup-phase SIG" + signalName + ". Output:\\n" + outputBuffer)
155+
.isEqualTo(0);
156+
}
157+
106158
private void assertGracefulShutdown(String signalName, boolean allowSigtermFallback) throws Exception {
107159
Path projectRoot = Path.of("").toAbsolutePath();
108160
String javaBin = Path.of(System.getProperty("java.home"), "bin", "java").toString();
@@ -196,6 +248,22 @@ private void sendSignal(long pid, String signalName) throws IOException, Interru
196248
}
197249
}
198250

251+
private boolean waitForOutputContains(StringBuilder outputBuffer, String marker, long timeout, TimeUnit unit)
252+
throws InterruptedException {
253+
long deadline = System.nanoTime() + unit.toNanos(timeout);
254+
while (System.nanoTime() < deadline) {
255+
synchronized (outputBuffer) {
256+
if (outputBuffer.indexOf(marker) >= 0) {
257+
return true;
258+
}
259+
}
260+
Thread.sleep(100);
261+
}
262+
synchronized (outputBuffer) {
263+
return outputBuffer.indexOf(marker) >= 0;
264+
}
265+
}
266+
199267
private void exerciseRemoteRepository(String serverUrl, StringBuilder outputBuffer)
200268
throws InterruptedException, RepositoryException, RepositoryConfigException {
201269
RemoteRepositoryManager manager = awaitRepositoryManager(serverUrl, outputBuffer);

0 commit comments

Comments
 (0)