PowerShell icon indicating copy to clipboard operation
PowerShell copied to clipboard

Wait-Event should allow more granularity

Open StartAutomating opened this issue 2 years ago • 7 comments

Summary of the new feature / enhancement

As a user of PowerShell events, I often find myself frustrated with the ways Wait-Event works.

Primarily this concerns the Timeout being in seconds.

That may seem like a little thing, but it has a major downside: when I'm expecting events to be quick, this makes me wait a whole second I may not need to wait.

Additionally, based off of the way the command is written, the shortest possible wait is 1/5th of a second.

Proposed technical implementation details (optional)

Wait-Event should have a -Timespan parameter that takes a particular Timespan.

Most of the internal logic should remain the same, with the exception of modifying the "floor" value, which is currently 200ms.

The floor value should instead be one tenth of the timeout.

StartAutomating avatar May 24 '23 01:05 StartAutomating

This is something I'm happy to handle myself as a small community improvement.

StartAutomating avatar May 24 '23 01:05 StartAutomating

I recommend full CancellationToken support rather than piecemeal timeout options

If you have CancellationToken support that gives you CancelAfter(TimeSpan) in addition to cancellation scenarios not involving time

My proof of concept demonstrates using CancellationToken with wait-event ( that is 100 milliseconds in the example below )

		$cancellationTokenSource = New-Object -Type System.Threading.CancellationTokenSource
		$cancellationToken = $cancellationTokenSource.Token
		try
		{
			try
			{
				Invoke-CommandWithCancellationToken -ScriptBlock {
					$cancellationTokenSource.CancelAfter(100)
					Wait-Event
				} -CancellationToken $cancellationToken -NoNewScope
			}
			catch
			{
				($PSItem.Exception.CancellationToken -eq $cancellationToken)
			}
		}
		finally
		{
			$cancellationTokenSource.Dispose()
		}

rhubarb-geek-nz avatar May 24 '23 02:05 rhubarb-geek-nz

FYI, in adding the -Timespan parameter and dropping down the polling interval to 1/100th of the -Timespan, I was able to get a response back from an event in ~34ms, instead of ~200ms: 588% faster!

StartAutomating avatar May 24 '23 02:05 StartAutomating

Why is Wait-Event dependent on anything to do with time or polling?

It should block and do absolutely nothing until an event has been queued and then unblock and process.

Polling, loops with timeouts all looks like a hack. Unfortunately that is what the code actually looks like... :(

            while (!received)
            {
                if (_timeoutInSeconds >= 0)
                {
                    if ((DateTime.UtcNow - startTime).TotalSeconds > _timeoutInSeconds)
                        break;
                }

                received = _eventArrived.WaitOne(200);

                eventManager.ProcessPendingActions();
            }

I hope that this code part means the WaitOne could be an infinite block as it sets the _eventArrived.

       private void NotifyEvent(PSEventArgs e)
        {
            if (_receivedEvent == null)
            {
                lock (_receivedEventLock)
                {
                    if (_receivedEvent == null)
                    {
                        _receivedEvent = e;
                        _eventArrived.Set();
                    }
                }
            }
        }

rhubarb-geek-nz avatar May 24 '23 02:05 rhubarb-geek-nz

@rhubarb-geek-nz yep, you've answered your own question.

I don't love the approach at play here either, but it was fairly straightforward to improve how it worked.

I'm not quite sure what a succinct integration of the CancellationToken would look like. If you want to provide a sample inline, I can see about updating the PR (your prior sample used some command that isn't in core).

StartAutomating avatar May 24 '23 02:05 StartAutomating

See https://github.com/rhubarb-geek-nz/CancellationTokenEvent/blob/main/CancellationTokenEvent/CommandWithCancellationToken.cs

The relevant part is ...

                using (CancellationTokenRegistration registration = CancellationToken.Register(() =>
                {
                    powerShell.Stop();
                }))
                {
                      ... run the long running operation. ...
                }

I can see about updating the PR (your prior sample used some command that isn't in core).

No, but it is in the PSGallery

rhubarb-geek-nz avatar May 24 '23 02:05 rhubarb-geek-nz

The loop should have calculated the timeout time before the loop and if there is no timeout it should use WaitOne, eg in pseudo code

if timeout then. calculate Expiry time

while (!received && !Stopping)
{
    if cancellationToken!=null and cancellationToken.IsCancellationRequested break
    if has timeout
          if current time later than expiry time then break
          _eventArrived.WaitOne(number of milliseconds remaining till timeout)
    else
         _eventArrived.WaitOne()

     ... etc ...
}

So if there is a timeout then the wait is for the exact number of milliseconds remaining, and if no timeout then it blocks completely. In the case of a cancellation token, the callback would call StopProcessing().

rhubarb-geek-nz avatar May 24 '23 05:05 rhubarb-geek-nz

The WG discussed this and since we've been adding support for timespan in other locations, it's very reasonable to include this for Wait-Event.

JamesWTruher avatar Feb 21 '24 17:02 JamesWTruher

The WG discussed this and since we've been adding support for timespan in other locations, it's very reasonable to include this for Wait-Event.

Good to hear. I've updated the PR for this (#19705) and the PSTransform attribute (#20299).

I'd very much like to see [TimeSpan] supported elsewhere as well. Please let me know if you have any other places that need it.

StartAutomating avatar Feb 21 '24 22:02 StartAutomating