/// <summary> /// Instructs the Process component to wait the specified number of milliseconds for the associated process to exit. /// </summary> private bool WaitForExitCore(int milliseconds) { SafeProcessHandle handle = null; try { handle = GetProcessHandle(Interop.Advapi32.ProcessOptions.SYNCHRONIZE, false); if (handle.IsInvalid) { return(true); } using (Interop.Kernel32.ProcessWaitHandle processWaitHandle = new Interop.Kernel32.ProcessWaitHandle(handle)) { return(_signaled = processWaitHandle.WaitOne(milliseconds)); } } finally { // If we have a hard timeout, we cannot wait for the streams if (_output != null && milliseconds == Timeout.Infinite) { _output.WaitUtilEOF(); } if (_error != null && milliseconds == Timeout.Infinite) { _error.WaitUtilEOF(); } handle?.Dispose(); } }
/// <devdoc> /// Make sure we are watching for a process exit. /// </devdoc> /// <internalonly/> private void EnsureWatchingForExit() { if (!_watchingForExit) { lock (this) { if (!_watchingForExit) { Debug.Assert(_haveProcessHandle, "Process.EnsureWatchingForExit called with no process handle"); Debug.Assert(Associated, "Process.EnsureWatchingForExit called with no associated process"); _watchingForExit = true; try { _waitHandle = new Interop.Kernel32.ProcessWaitHandle(_processHandle); _registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(_waitHandle, new WaitOrTimerCallback(CompletionCallback), _waitHandle, -1, true); } catch { _watchingForExit = false; throw; } } } } }
/// <devdoc> /// Gets a short-term handle to the process, with the given access. /// If a handle is stored in current process object, then use it. /// Note that the handle we stored in current process object will have all access we need. /// </devdoc> /// <internalonly/> private SafeProcessHandle GetProcessHandle(int access, bool throwIfExited) { #if FEATURE_TRACESWITCH Debug.WriteLineIf(_processTracing.TraceVerbose, "GetProcessHandle(access = 0x" + access.ToString("X8", CultureInfo.InvariantCulture) + ", throwIfExited = " + throwIfExited + ")"); #if DEBUG if (_processTracing.TraceVerbose) { StackFrame calledFrom = new StackTrace(true).GetFrame(0); Debug.WriteLine(" called from " + calledFrom.GetFileName() + ", line " + calledFrom.GetFileLineNumber()); } #endif #endif if (_haveProcessHandle) { if (throwIfExited) { // Since haveProcessHandle is true, we know we have the process handle // open with at least SYNCHRONIZE access, so we can wait on it with // zero timeout to see if the process has exited. using (Interop.Kernel32.ProcessWaitHandle waitHandle = new Interop.Kernel32.ProcessWaitHandle(_processHandle)) { if (waitHandle.WaitOne(0)) { if (_haveProcessId) { throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); } else { throw new InvalidOperationException(SR.ProcessHasExitedNoId); } } } } // If we dispose of our contained handle we'll be in a bad state. NetFX dealt with this // by doing a try..finally around every usage of GetProcessHandle and only disposed if // it wasn't our handle. return(new SafeProcessHandle(_processHandle.DangerousGetHandle(), ownsHandle: false)); } else { EnsureState(State.HaveId | State.IsLocal); SafeProcessHandle handle = SafeProcessHandle.InvalidHandle; handle = ProcessManager.OpenProcess(_processId, access, throwIfExited); if (throwIfExited && (access & Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION) != 0) { if (Interop.Kernel32.GetExitCodeProcess(handle, out _exitCode) && _exitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE) { throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); } } return(handle); } }
/// <summary>Checks whether the process has exited and updates state accordingly.</summary> private void UpdateHasExited() { using (SafeProcessHandle handle = GetProcessHandle( Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION | Interop.Advapi32.ProcessOptions.SYNCHRONIZE, false)) { if (handle.IsInvalid) { _exited = true; } else { int localExitCode; // Although this is the wrong way to check whether the process has exited, // it was historically the way we checked for it, and a lot of code then took a dependency on // the fact that this would always be set before the pipes were closed, so they would read // the exit code out after calling ReadToEnd() or standard output or standard error. In order // to allow 259 to function as a valid exit code and to break as few people as possible that // took the ReadToEnd dependency, we check for an exit code before doing the more correct // check to see if we have been signaled. if (Interop.Kernel32.GetExitCodeProcess(handle, out localExitCode) && localExitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE) { _exitCode = localExitCode; _exited = true; } else { // The best check for exit is that the kernel process object handle is invalid, // or that it is valid and signaled. Checking if the exit code != STILL_ACTIVE // does not guarantee the process is closed, // since some process could return an actual STILL_ACTIVE exit code (259). if (!_signaled) // if we just came from WaitForExit, don't repeat { using (var wh = new Interop.Kernel32.ProcessWaitHandle(handle)) { _signaled = wh.WaitOne(0); } } if (_signaled) { if (!Interop.Kernel32.GetExitCodeProcess(handle, out localExitCode)) { throw new Win32Exception(); } _exitCode = localExitCode; _exited = true; } } } } }
/// <devdoc> /// Gets a short-term handle to the process, with the given access. /// If a handle is stored in current process object, then use it. /// Note that the handle we stored in current process object will have all access we need. /// </devdoc> /// <internalonly/> private SafeProcessHandle GetProcessHandle(int access, bool throwIfExited) { if (_haveProcessHandle) { if (throwIfExited) { // Since haveProcessHandle is true, we know we have the process handle // open with at least SYNCHRONIZE access, so we can wait on it with // zero timeout to see if the process has exited. using (Interop.Kernel32.ProcessWaitHandle waitHandle = new Interop.Kernel32.ProcessWaitHandle(_processHandle)) { if (waitHandle.WaitOne(0)) { if (_haveProcessId) { throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); } else { throw new InvalidOperationException(SR.ProcessHasExitedNoId); } } } } // If we dispose of our contained handle we'll be in a bad state. NetFX dealt with this // by doing a try..finally around every usage of GetProcessHandle and only disposed if // it wasn't our handle. return(new SafeProcessHandle(_processHandle.DangerousGetHandle(), ownsHandle: false)); } else { EnsureState(State.HaveId | State.IsLocal); SafeProcessHandle handle = SafeProcessHandle.InvalidHandle; handle = ProcessManager.OpenProcess(_processId, access, throwIfExited); if (throwIfExited && (access & Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION) != 0) { if (Interop.Kernel32.GetExitCodeProcess(handle, out _exitCode) && _exitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE) { throw new InvalidOperationException(SR.Format(SR.ProcessHasExited, _processId.ToString(CultureInfo.CurrentCulture))); } } return(handle); } }
// Handle should be valid and have PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE access public static bool HasExited(SafeProcessHandle handle, ref bool signaled, out int exitCode) { // Although this is the wrong way to check whether the process has exited, // it was historically the way we checked for it, and a lot of code then took a dependency on // the fact that this would always be set before the pipes were closed, so they would read // the exit code out after calling ReadToEnd() or standard output or standard error. In order // to allow 259 to function as a valid exit code and to break as few people as possible that // took the ReadToEnd dependency, we check for an exit code before doing the more correct // check to see if we have been signaled. if (Interop.Kernel32.GetExitCodeProcess(handle, out exitCode) && exitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE) { return(true); } // The best check for exit is that the kernel process object handle is invalid, // or that it is valid and signaled. Checking if the exit code != STILL_ACTIVE // does not guarantee the process is closed, // since some process could return an actual STILL_ACTIVE exit code (259). if (!signaled) // if we just came from Process.WaitForExit, don't repeat { using (var wh = new Interop.Kernel32.ProcessWaitHandle(handle)) { signaled = wh.WaitOne(0); } } if (signaled) { if (!Interop.Kernel32.GetExitCodeProcess(handle, out exitCode)) { throw new Win32Exception(); } return(true); } return(false); }