1919import java .io .IOException ;
2020import java .io .InputStream ;
2121import java .io .OutputStream ;
22+ import java .lang .ref .Cleaner ;
2223import java .util .concurrent .ExecutionException ;
2324import java .util .concurrent .ExecutorService ;
2425import java .util .concurrent .Executors ;
2829
2930/** A Windows subprocess backed by a native object. */
3031public 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