diff --git a/src/com/pty4j/windows/WinPty.java b/src/com/pty4j/windows/WinPty.java index a51e845c..84a21cdb 100755 --- a/src/com/pty4j/windows/WinPty.java +++ b/src/com/pty4j/windows/WinPty.java @@ -13,7 +13,6 @@ import java.io.IOException; import static com.sun.jna.platform.win32.WinBase.INFINITE; -import static com.sun.jna.platform.win32.WinBase.WAIT_OBJECT_0; import static com.sun.jna.platform.win32.WinNT.GENERIC_READ; import static com.sun.jna.platform.win32.WinNT.GENERIC_WRITE; @@ -21,13 +20,14 @@ * @author traff */ public class WinPty { - private final Pointer myWinpty; + private Pointer myWinpty; + private WinNT.HANDLE myProcess = null; private NamedPipe myConinPipe; private NamedPipe myConoutPipe; private NamedPipe myConerrPipe; - private boolean myDone = false; + private boolean myChildExited = false; private int myStatus = -1; private boolean myClosed = false; @@ -88,15 +88,15 @@ public WinPty(String cmdline, String cwd, String env, boolean consoleMode) throw // Success! Save the values we want and let the `finally` block clean up the rest. - // Designate a thread to wait for the process to exit. That thread will - // also be responsible for closing the process handle. - new Reaper(processHandle.getValue()).start(); - myWinpty = winpty; + myProcess = processHandle.getValue(); myConinPipe = coninPipe; myConoutPipe = conoutPipe; myConerrPipe = conerrPipe; + // Designate a thread to wait for the process to exit. + new WaitForExitThread().start(); + winpty = null; processHandle.setValue(null); coninPipe = conoutPipe = conerrPipe = null; @@ -135,27 +135,57 @@ public synchronized void setWinSize(WinSize winSize) { INSTANCE.winpty_set_size(myWinpty, winSize.ws_col, winSize.ws_row, null); } + // Close the winpty_t object, which disconnects libwinpty from the winpty + // agent process. The agent will then close the hidden console, killing + // everything attached to it. public synchronized void close() { if (myClosed) { return; } INSTANCE.winpty_free(myWinpty); + myWinpty = null; myClosed = true; + closeUnusedProcessHandle(); } + private synchronized void closeUnusedProcessHandle() { + // Keep the process handle open until both conditions are met: + // 1. The process has exited. + // 2. We have disconnected from the agent, by closing the winpty_t + // object. + // As long as the process handle is open, Windows will not reuse the child + // process' PID. + // https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803 + if (myClosed && myChildExited && myProcess != null) { + Kernel32.INSTANCE.CloseHandle(myProcess); + myProcess = null; + } + } + + // Returns true if the child process is still running. The winpty_t and + // WinPty objects may be closed/freed either before or after the child + // process exits. public synchronized boolean isRunning() { - return !myDone; + return !myChildExited; } + // Waits for the child process to exit. public synchronized int waitFor() throws InterruptedException { - while (!myDone) { + while (!myChildExited) { wait(); } return myStatus; } + public synchronized int getChildProcessId() { + if (myClosed) { + return -1; + } + return Kernel32.INSTANCE.GetProcessId(myProcess); + } + public synchronized int exitValue() { - if (!myDone) { + if (!myChildExited) { throw new IllegalThreadStateException("Process not Terminated"); } return myStatus; @@ -188,24 +218,26 @@ public NamedPipe getErrorPipe() { return myConerrPipe; } - private class Reaper extends Thread { - private WinNT.HANDLE myProcess; + // It is mostly possible to avoid using this thread; instead, the above + // methods could call WaitForSingleObject themselves, using either a 0 or + // INFINITE timeout as appropriate. It is tricky, though, because we need + // to avoid closing the process handle as long as any threads are waiting on + // it, but we can't do an INFINITE wait inside a synchronized method. It + // could be done using an extra reference count, or by using DuplicateHandle + // for INFINITE waits. + private class WaitForExitThread extends Thread { private IntByReference myStatusByRef = new IntByReference(-1); - Reaper(WinNT.HANDLE process) { - myProcess = process; - } - @Override public void run() { Kernel32.INSTANCE.WaitForSingleObject(myProcess, INFINITE); Kernel32.INSTANCE.GetExitCodeProcess(myProcess, myStatusByRef); synchronized (WinPty.this) { - WinPty.this.myDone = true; + WinPty.this.myChildExited = true; WinPty.this.myStatus = myStatusByRef.getValue(); + closeUnusedProcessHandle(); WinPty.this.notifyAll(); } - Kernel32.INSTANCE.CloseHandle(myProcess); } } diff --git a/src/com/pty4j/windows/WinPtyProcess.java b/src/com/pty4j/windows/WinPtyProcess.java index ee805679..9fcca154 100644 --- a/src/com/pty4j/windows/WinPtyProcess.java +++ b/src/com/pty4j/windows/WinPtyProcess.java @@ -116,6 +116,10 @@ public int waitFor() throws InterruptedException { return myWinPty.waitFor(); } + public int getChildProcessId() { + return myWinPty.getChildProcessId(); + } + @Override public int exitValue() { return myWinPty.exitValue();