Skip to content

Commit 09fe68a

Browse files
benjaminpcopybara-github
authored andcommitted
Replace WindowsSubprocess finalizer with a Cleaner.
Closes bazelbuild#23474. PiperOrigin-RevId: 671698935 Change-Id: Ief4fb493d08c19fa0e7fb6313e52cb1bb88ec379
1 parent b1481db commit 09fe68a

File tree

1 file changed

+70
-47
lines changed

1 file changed

+70
-47
lines changed

src/main/java/com/google/devtools/build/lib/shell/WindowsSubprocess.java

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.io.InputStream;
2121
import java.io.OutputStream;
22+
import java.lang.ref.Cleaner;
2223
import java.util.concurrent.ExecutionException;
2324
import java.util.concurrent.ExecutorService;
2425
import java.util.concurrent.Executors;
@@ -28,6 +29,8 @@
2829

2930
/** A Windows subprocess backed by a native object. */
3031
public class WindowsSubprocess implements Subprocess {
32+
private static final Cleaner cleaner = Cleaner.create();
33+
3134
// For debugging purposes.
3235
private String commandLine;
3336

@@ -136,10 +139,46 @@ public Thread newThread(Runnable runnable) {
136139
}
137140
});
138141

139-
private volatile long nativeProcess;
140-
private final ProcessOutputStream stdinStream;
141-
private final ProcessInputStream stdoutStream;
142-
private final ProcessInputStream stderrStream;
142+
private static final class NativeState implements Runnable {
143+
private volatile long nativeProcess;
144+
private final ProcessOutputStream stdinStream;
145+
private final ProcessInputStream stdoutStream;
146+
private final ProcessInputStream stderrStream;
147+
148+
private NativeState(
149+
long nativeProcess,
150+
ProcessOutputStream stdinStream,
151+
ProcessInputStream stdoutStream,
152+
ProcessInputStream stderrStream) {
153+
this.nativeProcess = nativeProcess;
154+
this.stdinStream = stdinStream;
155+
this.stdoutStream = stdoutStream;
156+
this.stderrStream = stderrStream;
157+
}
158+
159+
@Override
160+
public void run() {
161+
if (nativeProcess == WindowsProcesses.INVALID) {
162+
return;
163+
}
164+
// The streams are null when redirected from/to files.
165+
if (stdinStream != null) {
166+
stdinStream.close();
167+
}
168+
if (stdoutStream != null) {
169+
stdoutStream.close();
170+
}
171+
if (stderrStream != null) {
172+
stderrStream.close();
173+
}
174+
var process = nativeProcess;
175+
nativeProcess = WindowsProcesses.INVALID;
176+
WindowsProcesses.deleteProcess(process);
177+
}
178+
}
179+
180+
private final NativeState nativeState;
181+
private final Cleaner.Cleanable cleanable;
143182
private final Future<WaitResult> processFuture;
144183
private final long timeoutMillis;
145184
private boolean timedout = false;
@@ -151,22 +190,27 @@ public Thread newThread(Runnable runnable) {
151190
boolean stderrRedirected,
152191
long timeoutMillis) {
153192
this.commandLine = commandLine;
154-
this.nativeProcess = nativeProcess;
193+
nativeState =
194+
new NativeState(
195+
nativeProcess,
196+
new ProcessOutputStream(),
197+
stdoutRedirected
198+
? null
199+
: new ProcessInputStream(WindowsProcesses.getStdout(nativeProcess)),
200+
stderrRedirected
201+
? null
202+
: new ProcessInputStream(WindowsProcesses.getStderr(nativeProcess)));
203+
cleanable = cleaner.register(this, nativeState);
155204
// As per the spec of Command, we should only apply timeouts that are > 0.
156205
this.timeoutMillis = timeoutMillis <= 0 ? -1 : timeoutMillis;
157-
stdoutStream =
158-
stdoutRedirected ? null : new ProcessInputStream(WindowsProcesses.getStdout(nativeProcess));
159-
stderrStream =
160-
stderrRedirected ? null : new ProcessInputStream(WindowsProcesses.getStderr(nativeProcess));
161-
stdinStream = new ProcessOutputStream();
162206
// Every Windows process we start consumes a thread here. This is suboptimal, but seems to be
163207
// the sanest way to reconcile WaitForMultipleObjects() and Java-style interruption.
164208
processFuture = WAITER_POOL.submit(this::waiterThreadFunc);
165209
}
166210

167211
// Waits for the process to finish.
168212
private WaitResult waiterThreadFunc() {
169-
switch (WindowsProcesses.waitFor(nativeProcess, timeoutMillis)) {
213+
switch (WindowsProcesses.waitFor(nativeState.nativeProcess, timeoutMillis)) {
170214
case 0:
171215
// Excellent, process finished in time.
172216
return WaitResult.SUCCESS;
@@ -180,35 +224,27 @@ private WaitResult waiterThreadFunc() {
180224
// Error. There isn't a lot we can do -- the process is still alive but
181225
// WaitForSingleObject() failed for some odd reason. This should
182226
// basically never happen, but if it does... let's get a stack trace.
183-
String errorMessage = WindowsProcesses.processGetLastError(nativeProcess);
227+
String errorMessage = WindowsProcesses.processGetLastError(nativeState.nativeProcess);
184228
throw new IllegalStateException(
185229
"Waiting for process "
186-
+ WindowsProcesses.getProcessPid(nativeProcess)
230+
+ WindowsProcesses.getProcessPid(nativeState.nativeProcess)
187231
+ " failed: "
188232
+ errorMessage);
189233
}
190234
}
191235

192-
@Override
193-
public synchronized void finalize() throws Throwable {
194-
if (nativeProcess != WindowsProcesses.INVALID) {
195-
close();
196-
}
197-
super.finalize();
198-
}
199-
200236
@Override
201237
public synchronized boolean destroy() {
202238
checkLiveness();
203-
return WindowsProcesses.terminate(nativeProcess);
239+
return WindowsProcesses.terminate(nativeState.nativeProcess);
204240
}
205241

206242
@Override
207243
public synchronized int exitValue() {
208244
checkLiveness();
209245

210-
int result = WindowsProcesses.getExitCode(nativeProcess);
211-
String error = WindowsProcesses.processGetLastError(nativeProcess);
246+
int result = WindowsProcesses.getExitCode(nativeState.nativeProcess);
247+
String error = WindowsProcesses.processGetLastError(nativeState.nativeProcess);
212248
if (!error.isEmpty()) {
213249
throw new IllegalStateException(error);
214250
}
@@ -245,36 +281,22 @@ public void waitFor() throws InterruptedException {
245281

246282
@Override
247283
public synchronized void close() {
248-
if (nativeProcess != WindowsProcesses.INVALID) {
249-
// The streams are null when redirected from/to files.
250-
if (stdinStream != null) {
251-
stdinStream.close();
252-
}
253-
if (stdoutStream != null) {
254-
stdoutStream.close();
255-
}
256-
if (stderrStream != null) {
257-
stderrStream.close();
258-
}
259-
long process = nativeProcess;
260-
nativeProcess = WindowsProcesses.INVALID;
261-
WindowsProcesses.deleteProcess(process);
262-
}
284+
cleanable.clean();
263285
}
264286

265287
@Override
266288
public OutputStream getOutputStream() {
267-
return stdinStream;
289+
return nativeState.stdinStream;
268290
}
269291

270292
@Override
271293
public InputStream getInputStream() {
272-
return stdoutStream;
294+
return nativeState.stdoutStream;
273295
}
274296

275297
@Override
276298
public InputStream getErrorStream() {
277-
return stderrStream;
299+
return nativeState.stderrStream;
278300
}
279301

280302
private synchronized void writeStdin(byte[] b, int off, int len) throws IOException {
@@ -283,11 +305,12 @@ private synchronized void writeStdin(byte[] b, int off, int len) throws IOExcept
283305
int remaining = len;
284306
int currentOffset = off;
285307
while (remaining != 0) {
286-
int written = WindowsProcesses.writeStdin(nativeProcess, b, currentOffset, remaining);
308+
int written =
309+
WindowsProcesses.writeStdin(nativeState.nativeProcess, b, currentOffset, remaining);
287310
// I think the Windows API never returns 0 in dwNumberOfBytesWritten
288311
// Verify.verify(written != 0);
289312
if (written == -1) {
290-
throw new IOException(WindowsProcesses.processGetLastError(nativeProcess));
313+
throw new IOException(WindowsProcesses.processGetLastError(nativeState.nativeProcess));
291314
}
292315

293316
remaining -= written;
@@ -297,11 +320,11 @@ private synchronized void writeStdin(byte[] b, int off, int len) throws IOExcept
297320

298321
private synchronized void closeStdin() {
299322
checkLiveness();
300-
WindowsProcesses.closeStdin(nativeProcess);
323+
WindowsProcesses.closeStdin(nativeState.nativeProcess);
301324
}
302325

303326
private void checkLiveness() {
304-
if (nativeProcess == WindowsProcesses.INVALID) {
327+
if (nativeState.nativeProcess == WindowsProcesses.INVALID) {
305328
throw new IllegalStateException();
306329
}
307330
}
@@ -313,6 +336,6 @@ public String toString() {
313336

314337
@Override
315338
public long getProcessId() {
316-
return this.nativeProcess;
339+
return nativeState.nativeProcess;
317340
}
318341
}

0 commit comments

Comments
 (0)