Implement SafeProcessHandle.WaitForExit* methods #126293

Description

@adamsitnik
namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
  • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
    /// <summary>
    /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
    /// </summary>
    publicboolCanceled{get;}
  • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
    privateProcessExitStatusWaitForExitCore()
    {
    usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
    processWaitHandle.WaitOne(Timeout.Infinite);
    returnnewProcessExitStatus(GetExitCode(),false);
    }
    privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
    {
    usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
    if(!processWaitHandle.WaitOne(milliseconds))
    {
    exitStatus=null;
    returnfalse;
    }
    exitStatus=newProcessExitStatus(GetExitCode(),false);
    returntrue;
    }
    privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
    {
    boolwasKilledOnTimeout=false;
    usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
    if(!processWaitHandle.WaitOne(milliseconds))
    {
    wasKilledOnTimeout=KillCore();
    processWaitHandle.WaitOne(Timeout.Infinite);
    }
    returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
    }
    privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
    {
    usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
    TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
    RegisteredWaitHandle?registeredWaitHandle=null;
    CancellationTokenRegistrationctr=default;
    try
    {
    registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
    processWaitHandle,
    (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
    tcs,
    Timeout.Infinite,
    executeOnlyOnce:true);
    if(cancellationToken.CanBeCanceled)
    {
    ctr=cancellationToken.UnsafeRegister(
    static state =>
    {
    var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
    taskSource.TrySetCanceled(token);
    },
    (tcs,cancellationToken));
    }
    awaittcs.Task.ConfigureAwait(false);
    }
    finally
    {
    ctr.Dispose();
    registeredWaitHandle?.Unregister(null);
    }
    returnnewProcessExitStatus(GetExitCode(),false);
    }
    privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
    {
    usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
    TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
    RegisteredWaitHandle?registeredWaitHandle=null;
    CancellationTokenRegistrationctr=default;
    StrongBox<bool>wasKilledBox=new(false);
    try
    {
    registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
    processWaitHandle,
    (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
    tcs,
    Timeout.Infinite,
    executeOnlyOnce:true);
    if(cancellationToken.CanBeCanceled)
    {
    ctr=cancellationToken.UnsafeRegister(
    static state =>
    {
    var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
    wasCancelled.Value=handle.KillCore();
    },
    (this,wasKilledBox));
    }
    awaittcs.Task.ConfigureAwait(false);
    }
    finally
    {
    ctr.Dispose();
    registeredWaitHandle?.Unregister(null);
    }
    returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
    }
  • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
    privateProcessExitStatusWaitForExitCore()
    {
    switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
    {
    case-1:
    thrownewWin32Exception();
    default:
    ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
    OnProcessExited();
    returnstatus;
    }
    }
    privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
    {
    switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
    {
    case-1:
    thrownewWin32Exception();
    case1:// timeout
    exitStatus=null;
    returnfalse;
    default:
    exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
    OnProcessExited();
    returntrue;
    }
    }
    privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
    privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
    {
    switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
    {
    case-1:
    thrownewWin32Exception();
    default:
    ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
    OnProcessExited();
    returnstatus;
    }
    }
    privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
    {
    if(!cancellationToken.CanBeCanceled)
    {
    returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
    }
    CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
    using(readHandle)
    using(writeHandle)
    {
    usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
    {
    ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
    },writeHandle);
    returnawaitTask.Run(()=>
    {
    switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
    {
    case-1:
    thrownewWin32Exception();
    case1:// canceled
    thrownewOperationCanceledException(cancellationToken);
    default:
    ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
    OnProcessExited();
    returnstatus;
    }
    },cancellationToken).ConfigureAwait(false);
    }
    }
    privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
    {
    if(!cancellationToken.CanBeCanceled)
    {
    returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
    }
    CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
    using(readHandle)
    using(writeHandle)
    {
    usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
    {
    ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
    },writeHandle);
    returnawaitTask.Run(()=>
    {
    switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
    {
    case-1:
    thrownewWin32Exception();
    case1:// canceled
    boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
    ProcessExitStatusstatus=WaitForExitCore();
    returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
    default:
    ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
    OnProcessExited();
    returnexitStatus;
    }
    },cancellationToken).ConfigureAwait(false);
    }
    }
  • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
  • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
  • On Unix, we need to integrate with existing logic for reaping processes
    • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
    • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
  • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    Implement SafeProcessHandle.WaitForExit* methods #126293

    Description

    @adamsitnik
    namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
    • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
      /// <summary>
      /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
      /// </summary>
      publicboolCanceled{get;}
    • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
      privateProcessExitStatusWaitForExitCore()
      {
      usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
      processWaitHandle.WaitOne(Timeout.Infinite);
      returnnewProcessExitStatus(GetExitCode(),false);
      }
      privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
      {
      usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
      if(!processWaitHandle.WaitOne(milliseconds))
      {
      exitStatus=null;
      returnfalse;
      }
      exitStatus=newProcessExitStatus(GetExitCode(),false);
      returntrue;
      }
      privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
      {
      boolwasKilledOnTimeout=false;
      usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
      if(!processWaitHandle.WaitOne(milliseconds))
      {
      wasKilledOnTimeout=KillCore();
      processWaitHandle.WaitOne(Timeout.Infinite);
      }
      returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
      }
      privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
      {
      usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
      TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
      RegisteredWaitHandle?registeredWaitHandle=null;
      CancellationTokenRegistrationctr=default;
      try
      {
      registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
      processWaitHandle,
      (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
      tcs,
      Timeout.Infinite,
      executeOnlyOnce:true);
      if(cancellationToken.CanBeCanceled)
      {
      ctr=cancellationToken.UnsafeRegister(
      static state =>
      {
      var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
      taskSource.TrySetCanceled(token);
      },
      (tcs,cancellationToken));
      }
      awaittcs.Task.ConfigureAwait(false);
      }
      finally
      {
      ctr.Dispose();
      registeredWaitHandle?.Unregister(null);
      }
      returnnewProcessExitStatus(GetExitCode(),false);
      }
      privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
      {
      usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
      TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
      RegisteredWaitHandle?registeredWaitHandle=null;
      CancellationTokenRegistrationctr=default;
      StrongBox<bool>wasKilledBox=new(false);
      try
      {
      registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
      processWaitHandle,
      (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
      tcs,
      Timeout.Infinite,
      executeOnlyOnce:true);
      if(cancellationToken.CanBeCanceled)
      {
      ctr=cancellationToken.UnsafeRegister(
      static state =>
      {
      var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
      wasCancelled.Value=handle.KillCore();
      },
      (this,wasKilledBox));
      }
      awaittcs.Task.ConfigureAwait(false);
      }
      finally
      {
      ctr.Dispose();
      registeredWaitHandle?.Unregister(null);
      }
      returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
      }
    • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
      privateProcessExitStatusWaitForExitCore()
      {
      switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
      {
      case-1:
      thrownewWin32Exception();
      default:
      ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
      OnProcessExited();
      returnstatus;
      }
      }
      privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
      {
      switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
      {
      case-1:
      thrownewWin32Exception();
      case1:// timeout
      exitStatus=null;
      returnfalse;
      default:
      exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
      OnProcessExited();
      returntrue;
      }
      }
      privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
      privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
      {
      switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
      {
      case-1:
      thrownewWin32Exception();
      default:
      ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
      OnProcessExited();
      returnstatus;
      }
      }
      privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
      {
      if(!cancellationToken.CanBeCanceled)
      {
      returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
      }
      CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
      using(readHandle)
      using(writeHandle)
      {
      usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
      {
      ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
      },writeHandle);
      returnawaitTask.Run(()=>
      {
      switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
      {
      case-1:
      thrownewWin32Exception();
      case1:// canceled
      thrownewOperationCanceledException(cancellationToken);
      default:
      ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
      OnProcessExited();
      returnstatus;
      }
      },cancellationToken).ConfigureAwait(false);
      }
      }
      privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
      {
      if(!cancellationToken.CanBeCanceled)
      {
      returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
      }
      CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
      using(readHandle)
      using(writeHandle)
      {
      usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
      {
      ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
      },writeHandle);
      returnawaitTask.Run(()=>
      {
      switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
      {
      case-1:
      thrownewWin32Exception();
      case1:// canceled
      boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
      ProcessExitStatusstatus=WaitForExitCore();
      returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
      default:
      ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
      OnProcessExited();
      returnexitStatus;
      }
      },cancellationToken).ConfigureAwait(false);
      }
      }
    • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
    • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
    • On Unix, we need to integrate with existing logic for reaping processes
      • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
      • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
    • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

    Metadata

    Metadata

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      Implement SafeProcessHandle.WaitForExit* methods #126293

      Description

      @adamsitnik
      namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
      • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
        /// <summary>
        /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
        /// </summary>
        publicboolCanceled{get;}
      • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
        privateProcessExitStatusWaitForExitCore()
        {
        usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
        processWaitHandle.WaitOne(Timeout.Infinite);
        returnnewProcessExitStatus(GetExitCode(),false);
        }
        privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
        {
        usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
        if(!processWaitHandle.WaitOne(milliseconds))
        {
        exitStatus=null;
        returnfalse;
        }
        exitStatus=newProcessExitStatus(GetExitCode(),false);
        returntrue;
        }
        privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
        {
        boolwasKilledOnTimeout=false;
        usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
        if(!processWaitHandle.WaitOne(milliseconds))
        {
        wasKilledOnTimeout=KillCore();
        processWaitHandle.WaitOne(Timeout.Infinite);
        }
        returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
        }
        privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
        {
        usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
        TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
        RegisteredWaitHandle?registeredWaitHandle=null;
        CancellationTokenRegistrationctr=default;
        try
        {
        registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
        processWaitHandle,
        (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
        tcs,
        Timeout.Infinite,
        executeOnlyOnce:true);
        if(cancellationToken.CanBeCanceled)
        {
        ctr=cancellationToken.UnsafeRegister(
        static state =>
        {
        var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
        taskSource.TrySetCanceled(token);
        },
        (tcs,cancellationToken));
        }
        awaittcs.Task.ConfigureAwait(false);
        }
        finally
        {
        ctr.Dispose();
        registeredWaitHandle?.Unregister(null);
        }
        returnnewProcessExitStatus(GetExitCode(),false);
        }
        privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
        {
        usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
        TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
        RegisteredWaitHandle?registeredWaitHandle=null;
        CancellationTokenRegistrationctr=default;
        StrongBox<bool>wasKilledBox=new(false);
        try
        {
        registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
        processWaitHandle,
        (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
        tcs,
        Timeout.Infinite,
        executeOnlyOnce:true);
        if(cancellationToken.CanBeCanceled)
        {
        ctr=cancellationToken.UnsafeRegister(
        static state =>
        {
        var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
        wasCancelled.Value=handle.KillCore();
        },
        (this,wasKilledBox));
        }
        awaittcs.Task.ConfigureAwait(false);
        }
        finally
        {
        ctr.Dispose();
        registeredWaitHandle?.Unregister(null);
        }
        returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
        }
      • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
        privateProcessExitStatusWaitForExitCore()
        {
        switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
        {
        case-1:
        thrownewWin32Exception();
        default:
        ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
        OnProcessExited();
        returnstatus;
        }
        }
        privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
        {
        switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
        {
        case-1:
        thrownewWin32Exception();
        case1:// timeout
        exitStatus=null;
        returnfalse;
        default:
        exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
        OnProcessExited();
        returntrue;
        }
        }
        privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
        privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
        {
        switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
        {
        case-1:
        thrownewWin32Exception();
        default:
        ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
        OnProcessExited();
        returnstatus;
        }
        }
        privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
        {
        if(!cancellationToken.CanBeCanceled)
        {
        returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
        }
        CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
        using(readHandle)
        using(writeHandle)
        {
        usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
        {
        ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
        },writeHandle);
        returnawaitTask.Run(()=>
        {
        switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
        {
        case-1:
        thrownewWin32Exception();
        case1:// canceled
        thrownewOperationCanceledException(cancellationToken);
        default:
        ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
        OnProcessExited();
        returnstatus;
        }
        },cancellationToken).ConfigureAwait(false);
        }
        }
        privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
        {
        if(!cancellationToken.CanBeCanceled)
        {
        returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
        }
        CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
        using(readHandle)
        using(writeHandle)
        {
        usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
        {
        ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
        },writeHandle);
        returnawaitTask.Run(()=>
        {
        switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
        {
        case-1:
        thrownewWin32Exception();
        case1:// canceled
        boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
        ProcessExitStatusstatus=WaitForExitCore();
        returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
        default:
        ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
        OnProcessExited();
        returnexitStatus;
        }
        },cancellationToken).ConfigureAwait(false);
        }
        }
      • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
      • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
      • On Unix, we need to integrate with existing logic for reaping processes
        • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
        • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
      • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

      Metadata

      Metadata

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        Implement SafeProcessHandle.WaitForExit* methods #126293

        Description

        @adamsitnik
        namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
        • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
          /// <summary>
          /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
          /// </summary>
          publicboolCanceled{get;}
        • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
          privateProcessExitStatusWaitForExitCore()
          {
          usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
          processWaitHandle.WaitOne(Timeout.Infinite);
          returnnewProcessExitStatus(GetExitCode(),false);
          }
          privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
          {
          usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
          if(!processWaitHandle.WaitOne(milliseconds))
          {
          exitStatus=null;
          returnfalse;
          }
          exitStatus=newProcessExitStatus(GetExitCode(),false);
          returntrue;
          }
          privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
          {
          boolwasKilledOnTimeout=false;
          usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
          if(!processWaitHandle.WaitOne(milliseconds))
          {
          wasKilledOnTimeout=KillCore();
          processWaitHandle.WaitOne(Timeout.Infinite);
          }
          returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
          }
          privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
          {
          usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
          TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
          RegisteredWaitHandle?registeredWaitHandle=null;
          CancellationTokenRegistrationctr=default;
          try
          {
          registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
          processWaitHandle,
          (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
          tcs,
          Timeout.Infinite,
          executeOnlyOnce:true);
          if(cancellationToken.CanBeCanceled)
          {
          ctr=cancellationToken.UnsafeRegister(
          static state =>
          {
          var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
          taskSource.TrySetCanceled(token);
          },
          (tcs,cancellationToken));
          }
          awaittcs.Task.ConfigureAwait(false);
          }
          finally
          {
          ctr.Dispose();
          registeredWaitHandle?.Unregister(null);
          }
          returnnewProcessExitStatus(GetExitCode(),false);
          }
          privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
          {
          usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
          TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
          RegisteredWaitHandle?registeredWaitHandle=null;
          CancellationTokenRegistrationctr=default;
          StrongBox<bool>wasKilledBox=new(false);
          try
          {
          registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
          processWaitHandle,
          (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
          tcs,
          Timeout.Infinite,
          executeOnlyOnce:true);
          if(cancellationToken.CanBeCanceled)
          {
          ctr=cancellationToken.UnsafeRegister(
          static state =>
          {
          var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
          wasCancelled.Value=handle.KillCore();
          },
          (this,wasKilledBox));
          }
          awaittcs.Task.ConfigureAwait(false);
          }
          finally
          {
          ctr.Dispose();
          registeredWaitHandle?.Unregister(null);
          }
          returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
          }
        • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
          privateProcessExitStatusWaitForExitCore()
          {
          switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
          {
          case-1:
          thrownewWin32Exception();
          default:
          ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
          OnProcessExited();
          returnstatus;
          }
          }
          privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
          {
          switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
          {
          case-1:
          thrownewWin32Exception();
          case1:// timeout
          exitStatus=null;
          returnfalse;
          default:
          exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
          OnProcessExited();
          returntrue;
          }
          }
          privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
          privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
          {
          switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
          {
          case-1:
          thrownewWin32Exception();
          default:
          ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
          OnProcessExited();
          returnstatus;
          }
          }
          privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
          {
          if(!cancellationToken.CanBeCanceled)
          {
          returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
          }
          CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
          using(readHandle)
          using(writeHandle)
          {
          usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
          {
          ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
          },writeHandle);
          returnawaitTask.Run(()=>
          {
          switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
          {
          case-1:
          thrownewWin32Exception();
          case1:// canceled
          thrownewOperationCanceledException(cancellationToken);
          default:
          ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
          OnProcessExited();
          returnstatus;
          }
          },cancellationToken).ConfigureAwait(false);
          }
          }
          privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
          {
          if(!cancellationToken.CanBeCanceled)
          {
          returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
          }
          CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
          using(readHandle)
          using(writeHandle)
          {
          usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
          {
          ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
          },writeHandle);
          returnawaitTask.Run(()=>
          {
          switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
          {
          case-1:
          thrownewWin32Exception();
          case1:// canceled
          boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
          ProcessExitStatusstatus=WaitForExitCore();
          returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
          default:
          ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
          OnProcessExited();
          returnexitStatus;
          }
          },cancellationToken).ConfigureAwait(false);
          }
          }
        • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
        • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
        • On Unix, we need to integrate with existing logic for reaping processes
          • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
          • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
        • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

        Metadata

        Metadata

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          Implement SafeProcessHandle.WaitForExit* methods #126293

          Description

          @adamsitnik
          namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
          • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
            /// <summary>
            /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
            /// </summary>
            publicboolCanceled{get;}
          • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
            privateProcessExitStatusWaitForExitCore()
            {
            usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
            processWaitHandle.WaitOne(Timeout.Infinite);
            returnnewProcessExitStatus(GetExitCode(),false);
            }
            privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
            {
            usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
            if(!processWaitHandle.WaitOne(milliseconds))
            {
            exitStatus=null;
            returnfalse;
            }
            exitStatus=newProcessExitStatus(GetExitCode(),false);
            returntrue;
            }
            privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
            {
            boolwasKilledOnTimeout=false;
            usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
            if(!processWaitHandle.WaitOne(milliseconds))
            {
            wasKilledOnTimeout=KillCore();
            processWaitHandle.WaitOne(Timeout.Infinite);
            }
            returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
            }
            privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
            {
            usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
            TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
            RegisteredWaitHandle?registeredWaitHandle=null;
            CancellationTokenRegistrationctr=default;
            try
            {
            registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
            processWaitHandle,
            (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
            tcs,
            Timeout.Infinite,
            executeOnlyOnce:true);
            if(cancellationToken.CanBeCanceled)
            {
            ctr=cancellationToken.UnsafeRegister(
            static state =>
            {
            var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
            taskSource.TrySetCanceled(token);
            },
            (tcs,cancellationToken));
            }
            awaittcs.Task.ConfigureAwait(false);
            }
            finally
            {
            ctr.Dispose();
            registeredWaitHandle?.Unregister(null);
            }
            returnnewProcessExitStatus(GetExitCode(),false);
            }
            privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
            {
            usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
            TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
            RegisteredWaitHandle?registeredWaitHandle=null;
            CancellationTokenRegistrationctr=default;
            StrongBox<bool>wasKilledBox=new(false);
            try
            {
            registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
            processWaitHandle,
            (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
            tcs,
            Timeout.Infinite,
            executeOnlyOnce:true);
            if(cancellationToken.CanBeCanceled)
            {
            ctr=cancellationToken.UnsafeRegister(
            static state =>
            {
            var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
            wasCancelled.Value=handle.KillCore();
            },
            (this,wasKilledBox));
            }
            awaittcs.Task.ConfigureAwait(false);
            }
            finally
            {
            ctr.Dispose();
            registeredWaitHandle?.Unregister(null);
            }
            returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
            }
          • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
            privateProcessExitStatusWaitForExitCore()
            {
            switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
            {
            case-1:
            thrownewWin32Exception();
            default:
            ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
            OnProcessExited();
            returnstatus;
            }
            }
            privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
            {
            switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
            {
            case-1:
            thrownewWin32Exception();
            case1:// timeout
            exitStatus=null;
            returnfalse;
            default:
            exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
            OnProcessExited();
            returntrue;
            }
            }
            privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
            privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
            {
            switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
            {
            case-1:
            thrownewWin32Exception();
            default:
            ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
            OnProcessExited();
            returnstatus;
            }
            }
            privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
            {
            if(!cancellationToken.CanBeCanceled)
            {
            returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
            }
            CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
            using(readHandle)
            using(writeHandle)
            {
            usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
            {
            ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
            },writeHandle);
            returnawaitTask.Run(()=>
            {
            switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
            {
            case-1:
            thrownewWin32Exception();
            case1:// canceled
            thrownewOperationCanceledException(cancellationToken);
            default:
            ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
            OnProcessExited();
            returnstatus;
            }
            },cancellationToken).ConfigureAwait(false);
            }
            }
            privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
            {
            if(!cancellationToken.CanBeCanceled)
            {
            returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
            }
            CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
            using(readHandle)
            using(writeHandle)
            {
            usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
            {
            ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
            },writeHandle);
            returnawaitTask.Run(()=>
            {
            switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
            {
            case-1:
            thrownewWin32Exception();
            case1:// canceled
            boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
            ProcessExitStatusstatus=WaitForExitCore();
            returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
            default:
            ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
            OnProcessExited();
            returnexitStatus;
            }
            },cancellationToken).ConfigureAwait(false);
            }
            }
          • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
          • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
          • On Unix, we need to integrate with existing logic for reaping processes
            • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
            • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
          • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

          Metadata

          Metadata

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            Implement SafeProcessHandle.WaitForExit* methods #126293

            Description

            @adamsitnik
            namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
            • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
              /// <summary>
              /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
              /// </summary>
              publicboolCanceled{get;}
            • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
              privateProcessExitStatusWaitForExitCore()
              {
              usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
              processWaitHandle.WaitOne(Timeout.Infinite);
              returnnewProcessExitStatus(GetExitCode(),false);
              }
              privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
              {
              usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
              if(!processWaitHandle.WaitOne(milliseconds))
              {
              exitStatus=null;
              returnfalse;
              }
              exitStatus=newProcessExitStatus(GetExitCode(),false);
              returntrue;
              }
              privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
              {
              boolwasKilledOnTimeout=false;
              usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
              if(!processWaitHandle.WaitOne(milliseconds))
              {
              wasKilledOnTimeout=KillCore();
              processWaitHandle.WaitOne(Timeout.Infinite);
              }
              returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
              }
              privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
              {
              usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
              TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
              RegisteredWaitHandle?registeredWaitHandle=null;
              CancellationTokenRegistrationctr=default;
              try
              {
              registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
              processWaitHandle,
              (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
              tcs,
              Timeout.Infinite,
              executeOnlyOnce:true);
              if(cancellationToken.CanBeCanceled)
              {
              ctr=cancellationToken.UnsafeRegister(
              static state =>
              {
              var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
              taskSource.TrySetCanceled(token);
              },
              (tcs,cancellationToken));
              }
              awaittcs.Task.ConfigureAwait(false);
              }
              finally
              {
              ctr.Dispose();
              registeredWaitHandle?.Unregister(null);
              }
              returnnewProcessExitStatus(GetExitCode(),false);
              }
              privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
              {
              usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
              TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
              RegisteredWaitHandle?registeredWaitHandle=null;
              CancellationTokenRegistrationctr=default;
              StrongBox<bool>wasKilledBox=new(false);
              try
              {
              registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
              processWaitHandle,
              (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
              tcs,
              Timeout.Infinite,
              executeOnlyOnce:true);
              if(cancellationToken.CanBeCanceled)
              {
              ctr=cancellationToken.UnsafeRegister(
              static state =>
              {
              var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
              wasCancelled.Value=handle.KillCore();
              },
              (this,wasKilledBox));
              }
              awaittcs.Task.ConfigureAwait(false);
              }
              finally
              {
              ctr.Dispose();
              registeredWaitHandle?.Unregister(null);
              }
              returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
              }
            • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
              privateProcessExitStatusWaitForExitCore()
              {
              switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
              {
              case-1:
              thrownewWin32Exception();
              default:
              ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
              OnProcessExited();
              returnstatus;
              }
              }
              privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
              {
              switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
              {
              case-1:
              thrownewWin32Exception();
              case1:// timeout
              exitStatus=null;
              returnfalse;
              default:
              exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
              OnProcessExited();
              returntrue;
              }
              }
              privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
              privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
              {
              switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
              {
              case-1:
              thrownewWin32Exception();
              default:
              ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
              OnProcessExited();
              returnstatus;
              }
              }
              privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
              {
              if(!cancellationToken.CanBeCanceled)
              {
              returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
              }
              CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
              using(readHandle)
              using(writeHandle)
              {
              usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
              {
              ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
              },writeHandle);
              returnawaitTask.Run(()=>
              {
              switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
              {
              case-1:
              thrownewWin32Exception();
              case1:// canceled
              thrownewOperationCanceledException(cancellationToken);
              default:
              ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
              OnProcessExited();
              returnstatus;
              }
              },cancellationToken).ConfigureAwait(false);
              }
              }
              privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
              {
              if(!cancellationToken.CanBeCanceled)
              {
              returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
              }
              CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
              using(readHandle)
              using(writeHandle)
              {
              usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
              {
              ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
              },writeHandle);
              returnawaitTask.Run(()=>
              {
              switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
              {
              case-1:
              thrownewWin32Exception();
              case1:// canceled
              boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
              ProcessExitStatusstatus=WaitForExitCore();
              returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
              default:
              ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
              OnProcessExited();
              returnexitStatus;
              }
              },cancellationToken).ConfigureAwait(false);
              }
              }
            • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
            • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
            • On Unix, we need to integrate with existing logic for reaping processes
              • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
              • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
            • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

            Metadata

            Metadata

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Implement SafeProcessHandle.WaitForExit* methods #126293

              Description

              @adamsitnik
              namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
              • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
                /// <summary>
                /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
                /// </summary>
                publicboolCanceled{get;}
              • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
                privateProcessExitStatusWaitForExitCore()
                {
                usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                processWaitHandle.WaitOne(Timeout.Infinite);
                returnnewProcessExitStatus(GetExitCode(),false);
                }
                privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
                {
                usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                if(!processWaitHandle.WaitOne(milliseconds))
                {
                exitStatus=null;
                returnfalse;
                }
                exitStatus=newProcessExitStatus(GetExitCode(),false);
                returntrue;
                }
                privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
                {
                boolwasKilledOnTimeout=false;
                usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                if(!processWaitHandle.WaitOne(milliseconds))
                {
                wasKilledOnTimeout=KillCore();
                processWaitHandle.WaitOne(Timeout.Infinite);
                }
                returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
                }
                privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
                {
                usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
                RegisteredWaitHandle?registeredWaitHandle=null;
                CancellationTokenRegistrationctr=default;
                try
                {
                registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
                processWaitHandle,
                (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
                tcs,
                Timeout.Infinite,
                executeOnlyOnce:true);
                if(cancellationToken.CanBeCanceled)
                {
                ctr=cancellationToken.UnsafeRegister(
                static state =>
                {
                var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
                taskSource.TrySetCanceled(token);
                },
                (tcs,cancellationToken));
                }
                awaittcs.Task.ConfigureAwait(false);
                }
                finally
                {
                ctr.Dispose();
                registeredWaitHandle?.Unregister(null);
                }
                returnnewProcessExitStatus(GetExitCode(),false);
                }
                privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
                {
                usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
                RegisteredWaitHandle?registeredWaitHandle=null;
                CancellationTokenRegistrationctr=default;
                StrongBox<bool>wasKilledBox=new(false);
                try
                {
                registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
                processWaitHandle,
                (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
                tcs,
                Timeout.Infinite,
                executeOnlyOnce:true);
                if(cancellationToken.CanBeCanceled)
                {
                ctr=cancellationToken.UnsafeRegister(
                static state =>
                {
                var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
                wasCancelled.Value=handle.KillCore();
                },
                (this,wasKilledBox));
                }
                awaittcs.Task.ConfigureAwait(false);
                }
                finally
                {
                ctr.Dispose();
                registeredWaitHandle?.Unregister(null);
                }
                returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
                }
              • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
                privateProcessExitStatusWaitForExitCore()
                {
                switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
                {
                case-1:
                thrownewWin32Exception();
                default:
                ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                OnProcessExited();
                returnstatus;
                }
                }
                privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
                {
                switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
                {
                case-1:
                thrownewWin32Exception();
                case1:// timeout
                exitStatus=null;
                returnfalse;
                default:
                exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                OnProcessExited();
                returntrue;
                }
                }
                privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
                privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
                {
                switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
                {
                case-1:
                thrownewWin32Exception();
                default:
                ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
                OnProcessExited();
                returnstatus;
                }
                }
                privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
                {
                if(!cancellationToken.CanBeCanceled)
                {
                returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
                }
                CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
                using(readHandle)
                using(writeHandle)
                {
                usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
                {
                ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
                },writeHandle);
                returnawaitTask.Run(()=>
                {
                switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
                {
                case-1:
                thrownewWin32Exception();
                case1:// canceled
                thrownewOperationCanceledException(cancellationToken);
                default:
                ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                OnProcessExited();
                returnstatus;
                }
                },cancellationToken).ConfigureAwait(false);
                }
                }
                privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
                {
                if(!cancellationToken.CanBeCanceled)
                {
                returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
                }
                CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
                using(readHandle)
                using(writeHandle)
                {
                usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
                {
                ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
                },writeHandle);
                returnawaitTask.Run(()=>
                {
                switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
                {
                case-1:
                thrownewWin32Exception();
                case1:// canceled
                boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
                ProcessExitStatusstatus=WaitForExitCore();
                returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
                default:
                ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                OnProcessExited();
                returnexitStatus;
                }
                },cancellationToken).ConfigureAwait(false);
                }
                }
              • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
              • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
              • On Unix, we need to integrate with existing logic for reaping processes
                • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
                • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
              • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

              Metadata

              Metadata

              Type

              No type

              Projects

              No projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                Implement SafeProcessHandle.WaitForExit* methods #126293

                Description

                @adamsitnik
                namespaceMicrosoft.Win32.SafeHandles{publicpartialclassSafeProcessHandle{publicProcessExitStatusWaitForExit();publicboolTryWaitForExit(TimeSpantimeout,[NotNullWhen(true)]outProcessExitStatus?exitStatus);publicProcessExitStatusWaitForExitOrKillOnTimeout(TimeSpantimeout);publicTask<ProcessExitStatus>WaitForExitAsync(CancellationTokencancellationToken=default);publicTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsync(CancellationTokencancellationToken);}}
                • The WaitForExitOrKillOnTimeout and WaitForExitOrKillOnCancellationAsync methods must not throw on timeout/cancellation, just kill the process (if it has not already exited) and return ProcessExitStatus with Canceled=true (if the process was actually killed)
                  /// <summary>
                  /// Gets a value indicating whether the process has been terminated due to timeout or cancellation.
                  /// </summary>
                  publicboolCanceled{get;}
                • Working Windows implementation was already provided in Add new process management APIs to SafeProcessHandle #124375:
                  privateProcessExitStatusWaitForExitCore()
                  {
                  usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                  processWaitHandle.WaitOne(Timeout.Infinite);
                  returnnewProcessExitStatus(GetExitCode(),false);
                  }
                  privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
                  {
                  usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                  if(!processWaitHandle.WaitOne(milliseconds))
                  {
                  exitStatus=null;
                  returnfalse;
                  }
                  exitStatus=newProcessExitStatus(GetExitCode(),false);
                  returntrue;
                  }
                  privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
                  {
                  boolwasKilledOnTimeout=false;
                  usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                  if(!processWaitHandle.WaitOne(milliseconds))
                  {
                  wasKilledOnTimeout=KillCore();
                  processWaitHandle.WaitOne(Timeout.Infinite);
                  }
                  returnnewProcessExitStatus(GetExitCode(),wasKilledOnTimeout);
                  }
                  privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
                  {
                  usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                  TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
                  RegisteredWaitHandle?registeredWaitHandle=null;
                  CancellationTokenRegistrationctr=default;
                  try
                  {
                  registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
                  processWaitHandle,
                  (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
                  tcs,
                  Timeout.Infinite,
                  executeOnlyOnce:true);
                  if(cancellationToken.CanBeCanceled)
                  {
                  ctr=cancellationToken.UnsafeRegister(
                  static state =>
                  {
                  var(taskSource,token)=((TaskCompletionSource<bool>taskSource,CancellationTokentoken))state!;
                  taskSource.TrySetCanceled(token);
                  },
                  (tcs,cancellationToken));
                  }
                  awaittcs.Task.ConfigureAwait(false);
                  }
                  finally
                  {
                  ctr.Dispose();
                  registeredWaitHandle?.Unregister(null);
                  }
                  returnnewProcessExitStatus(GetExitCode(),false);
                  }
                  privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
                  {
                  usingInterop.Kernel32.ProcessWaitHandleprocessWaitHandle=new(this);
                  TaskCompletionSource<bool>tcs=new(TaskCreationOptions.RunContinuationsAsynchronously);
                  RegisteredWaitHandle?registeredWaitHandle=null;
                  CancellationTokenRegistrationctr=default;
                  StrongBox<bool>wasKilledBox=new(false);
                  try
                  {
                  registeredWaitHandle=ThreadPool.RegisterWaitForSingleObject(
                  processWaitHandle,
                  (state,timedOut)=>((TaskCompletionSource<bool>)state!).TrySetResult(true),
                  tcs,
                  Timeout.Infinite,
                  executeOnlyOnce:true);
                  if(cancellationToken.CanBeCanceled)
                  {
                  ctr=cancellationToken.UnsafeRegister(
                  static state =>
                  {
                  var(handle,wasCancelled)=((SafeProcessHandle,StrongBox<bool>))state!;
                  wasCancelled.Value=handle.KillCore();
                  },
                  (this,wasKilledBox));
                  }
                  awaittcs.Task.ConfigureAwait(false);
                  }
                  finally
                  {
                  ctr.Dispose();
                  registeredWaitHandle?.Unregister(null);
                  }
                  returnnewProcessExitStatus(GetExitCode(),wasKilledBox.Value);
                  }
                • Working macOS implementation was already provided in Implement SafeProcessHandle APIs for macOS #124923. It needs to be used for FreeBSD as well:
                  privateProcessExitStatusWaitForExitCore()
                  {
                  switch(Interop.Sys.WaitForExitAndReap(this,ProcessId,outintexitCode,outintrawSignal))
                  {
                  case-1:
                  thrownewWin32Exception();
                  default:
                  ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                  OnProcessExited();
                  returnstatus;
                  }
                  }
                  privateboolTryWaitForExitCore(intmilliseconds,[NotNullWhen(true)]outProcessExitStatus?exitStatus)
                  {
                  switch(Interop.Sys.TryWaitForExit(this,ProcessId,milliseconds,outintexitCode,outintrawSignal))
                  {
                  case-1:
                  thrownewWin32Exception();
                  case1:// timeout
                  exitStatus=null;
                  returnfalse;
                  default:
                  exitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                  OnProcessExited();
                  returntrue;
                  }
                  }
                  privatestaticintGetProcessIdCore()=>thrownewPlatformNotSupportedException();
                  privateProcessExitStatusWaitForExitOrKillOnTimeoutCore(intmilliseconds)
                  {
                  switch(Interop.Sys.WaitForExitOrKillOnTimeout(this,ProcessId,_isGroupLeader,milliseconds,outintexitCode,outintrawSignal,outinthasTimedout))
                  {
                  case-1:
                  thrownewWin32Exception();
                  default:
                  ProcessExitStatusstatus=new(exitCode,hasTimedout==1,rawSignal!=0?(PosixSignal)rawSignal:null);
                  OnProcessExited();
                  returnstatus;
                  }
                  }
                  privateasyncTask<ProcessExitStatus>WaitForExitAsyncCore(CancellationTokencancellationToken)
                  {
                  if(!cancellationToken.CanBeCanceled)
                  {
                  returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
                  }
                  CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
                  using(readHandle)
                  using(writeHandle)
                  {
                  usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
                  {
                  ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
                  },writeHandle);
                  returnawaitTask.Run(()=>
                  {
                  switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
                  {
                  case-1:
                  thrownewWin32Exception();
                  case1:// canceled
                  thrownewOperationCanceledException(cancellationToken);
                  default:
                  ProcessExitStatusstatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                  OnProcessExited();
                  returnstatus;
                  }
                  },cancellationToken).ConfigureAwait(false);
                  }
                  }
                  privateasyncTask<ProcessExitStatus>WaitForExitOrKillOnCancellationAsyncCore(CancellationTokencancellationToken)
                  {
                  if(!cancellationToken.CanBeCanceled)
                  {
                  returnawaitTask.Run(WaitForExitCore,cancellationToken).ConfigureAwait(false);
                  }
                  CreatePipe(outSafeFileHandlereadHandle,outSafeFileHandlewriteHandle);
                  using(readHandle)
                  using(writeHandle)
                  {
                  usingCancellationTokenRegistrationregistration=cancellationToken.Register(static state =>
                  {
                  ((SafeFileHandle)state!).Close();// Close the write end of the pipe to signal cancellation
                  },writeHandle);
                  returnawaitTask.Run(()=>
                  {
                  switch(Interop.Sys.TryWaitForExitCancellable(this,ProcessId,(int)readHandle.DangerousGetHandle(),outintexitCode,outintrawSignal))
                  {
                  case-1:
                  thrownewWin32Exception();
                  case1:// canceled
                  boolwasKilled=KillCore(throwOnError:false,entireProcessGroup:_isGroupLeader);
                  ProcessExitStatusstatus=WaitForExitCore();
                  returnnewProcessExitStatus(status.ExitCode,wasKilled,status.Signal);
                  default:
                  ProcessExitStatusexitStatus=new(exitCode,false,rawSignal!=0?(PosixSignal)rawSignal:null);
                  OnProcessExited();
                  returnexitStatus;
                  }
                  },cancellationToken).ConfigureAwait(false);
                  }
                  }
                • A draft on Linux implementation that uses pidfd and poll to wait for process exit was provided in Implement SafeProcessHandle APIs for Linux and other Unixes #124979
                • The Unix implementation needs to use SafeFileHandle.CreateAnonymousPipe API for creating pipe that is used for monitoring for cancellation
                • On Unix, we need to integrate with existing logic for reaping processes
                  • the new Wait methods need to be able to reap the process, but once they do they need to update ProcessWaitState
                  • if ProcessWaitState reaps the process first, the Wait* methods should obtain the exit status from there
                • the tests should go to src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.cs introduced by Introduce SafeProcessHandle.Start and ProcessId #126192 and reuse tests provided in Add new process management APIs to SafeProcessHandle #124375 and Implement SafeProcessHandle APIs for macOS #124923

                Metadata

                Metadata

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions