Skip to content

Commit 17707be

Browse files
authored
fix: Fix race in emulator controller (#2836)
Fix TSan data race in EmulatorController by synchronizing logging threads. The race condition occurred when a previous emulator process instance was shutting down while a new instance was starting. The logging threads for stdout/stderr of the previous process could still be reading from file descriptors that were subsequently reused for the new process's pipes. This fix ensures that: 1. Logging threads are tracked. 2. EmulatorController.stop() joins these threads after destroying the process. 3. EmulatorController.stop() waits for the process to exit completely.
1 parent b7a0232 commit 17707be

1 file changed

Lines changed: 23 additions & 3 deletions

File tree

google-cloud-bigtable-emulator-core/src/main/java/com/google/cloud/bigtable/emulator/core/EmulatorController.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public class EmulatorController {
4444

4545
private final Path executable;
4646
private Process process;
47+
private Thread stdoutThread;
48+
private Thread stderrThread;
4749
private boolean isStopped = true;
4850
private Thread shutdownHook;
4951

@@ -127,8 +129,8 @@ public synchronized void start(int port)
127129
throw e;
128130
}
129131
}
130-
pipeStreamToLog(process.getInputStream(), Level.INFO);
131-
pipeStreamToLog(process.getErrorStream(), Level.WARNING);
132+
Thread stdoutThread = pipeStreamToLog(process.getInputStream(), Level.INFO);
133+
Thread stderrThread = pipeStreamToLog(process.getErrorStream(), Level.WARNING);
132134
isStopped = false;
133135

134136
shutdownHook =
@@ -164,6 +166,23 @@ public synchronized void stop() {
164166
} finally {
165167
isStopped = true;
166168
process.destroy();
169+
170+
try {
171+
process.waitFor();
172+
if (stdoutThread != null) {
173+
stdoutThread.join();
174+
}
175+
if (stderrThread != null) {
176+
stderrThread.join();
177+
}
178+
} catch (InterruptedException e) {
179+
Thread.currentThread().interrupt();
180+
LOGGER.log(Level.WARNING, "Interrupted while waiting for emulator to stop", e);
181+
} finally {
182+
stdoutThread = null;
183+
stderrThread = null;
184+
process = null;
185+
}
167186
}
168187
}
169188

@@ -239,7 +258,7 @@ private static void waitForPort(int port) throws InterruptedException, TimeoutEx
239258
}
240259

241260
/** Creates a thread that will pipe an {@link InputStream} to this class' Logger. */
242-
private static void pipeStreamToLog(final InputStream stream, final Level level) {
261+
private static Thread pipeStreamToLog(final InputStream stream, final Level level) {
243262
final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
244263

245264
Thread thread =
@@ -258,6 +277,7 @@ private static void pipeStreamToLog(final InputStream stream, final Level level)
258277
});
259278
thread.setDaemon(true);
260279
thread.start();
280+
return thread;
261281
}
262282
// </editor-fold>
263283
}

0 commit comments

Comments
 (0)