Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions btrace-client/src/main/java/io/btrace/client/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,19 @@ public synchronized void close() throws IOException {
// Mark as disconnected to prevent shutdown hook from attempting further sends
disconnected = true;
IOException failure = null;
// Release a reader before closing the protocol. A command loop blocks in a read with no
// timeout -- V2 negotiation sets one and restores the previous value, which is unlimited -- so
// another thread may be inside a socket read when close() runs. Closing the protocol streams
// first means waiting on that reader to leave, which is the wrong order to depend on: shutting
// the input down makes the read return, and the stream closes below then have nothing to wait
// for.
try {
if (sock != null && !sock.isClosed() && !sock.isInputShutdown()) {
sock.shutdownInput();
}
} catch (IOException ignored) {
// Best effort: an already-reset connection is fine, the closes below still run.
}
try {
if (protocol != null) {
protocol.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -544,7 +543,7 @@ public void testOnMethodUnattended() throws Exception {
assertNotNull(code, "BTrace compilation failed");

CompletableFuture<String> probeIdFuture = new CompletableFuture<>();
ExecutorService executor = Executors.newSingleThreadExecutor();
ExecutorService executor = newDaemonExecutor("functional-probe-submit");
Future<?> submitFuture =
executor.submit(
() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -84,7 +83,7 @@ void isolatesThrowingHandlersAndCompletesTerminalMBeanCleanup() throws Exception
assertNotNull(ready, "target did not report its PID");
String pid = ready.substring("ready:".length());
Client client = null;
ExecutorService executor = Executors.newSingleThreadExecutor();
ExecutorService executor = newDaemonExecutor("runtime-hardening-probe-submit");
try (PrintWriter targetInput = new PrintWriter(target.getOutputStream(), true)) {
try {
File trace = locateTrace("btrace/Issue888RuntimeHardeningTest.java");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -188,7 +187,7 @@ private void assertProbeRoundTrip(
File traceFile = locateTrace("btrace/OnTimerArgTest.java");
assertNotNull(traceFile, "Test probe source is missing");
Client client = createClient();
ExecutorService executor = Executors.newSingleThreadExecutor();
ExecutorService executor = newDaemonExecutor("prepared-mode-probe-submit");
CompletableFuture<Void> started = new CompletableFuture<>();
try {
client.attach(String.valueOf(pid), null, getEventsClassPath());
Expand Down
35 changes: 34 additions & 1 deletion integration-tests/src/test/java/tests/RuntimeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -142,6 +144,25 @@ protected static String resolveTestJavaHome() {
+ " JAVA_HOME or the java.home system property is set");
}

/**
* Creates a single-threaded executor whose thread is a daemon.
*
* <p>Tests submit probe work to a worker thread and tear it down with {@code shutdownNow}, which
* interrupts. A thread blocked in socket I/O does not answer an interrupt, and a non-daemon
* worker in that state keeps the test JVM alive after the class finishes, so the build stalls
* with nothing to report. A daemon thread cannot hold the JVM open.
*
* @param name thread name, to make a stuck worker identifiable in a dump
*/
protected static ExecutorService newDaemonExecutor(String name) {
return Executors.newSingleThreadExecutor(
runnable -> {
Thread t = new Thread(runnable, name);
t.setDaemon(true);
return t;
});
}

public static void classSetup() {
if (System.getProperty("btrace.comm.protocol") == null) {
System.setProperty("btrace.comm.protocol", "2");
Expand Down Expand Up @@ -916,6 +937,12 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc)
}

public static final class TestApp {
/** How long a target gets to exit on its own after being told to stop. */
private static final int SHUTDOWN_TIMEOUT_SECONDS = 30;

/** How long it then gets after being destroyed forcibly. */
private static final int FORCED_SHUTDOWN_TIMEOUT_SECONDS = 10;

private int pid;
private final CountDownLatch testAppLatch = new CountDownLatch(1);
private final Process process;
Expand Down Expand Up @@ -1000,7 +1027,13 @@ public void stop() throws InterruptedException {
PrintWriter pw = new PrintWriter(process.getOutputStream());
pw.println("done");
pw.flush();
process.waitFor();
// Bounded: a target that ignores "done" -- because the agent still holds it, or a probe
// never detached -- used to block the test here indefinitely, which surfaces as a job that
// burns its entire timeout with no failing assertion and leaves the JVM behind.
if (!process.waitFor(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
process.destroyForcibly();
process.waitFor(FORCED_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
}
}

Expand Down
Loading