Skip to content

Conversation

@MattKotsenas
Copy link
Member

This PR implements the API proposal dotnet/corefx#34689: Add a task-based WaitForExitAsync() for the Process class.

In order to ensure that the Exited event is never lost, it is the caller's responsibility to ensure that EnableRaisingEvents is true prior to calling WaitForExitAsync(). A new InvalidOperationException with an appropriate error message is introduced to enforce those semantics.

Additionally, per feedback, WaitForExitAsync returns a Task instead of more closely matching the sync version and returning Task<bool>. To determine if the process has exited,
callers can check HasExited after the await (also cancellation follows the async pattern of throwing an OperationCanceledException and setting canceled on the task).

Unit tests for WaitForExit() were duplicated to add async versions, and one sync test was missing an assert, so I added it.

Copy link
Contributor

@scalablecory scalablecory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, though minor efficiency change noted. I also think the tests can be shortened a little bit.

@MattKotsenas MattKotsenas force-pushed the feature/process-waitforexitasync branch 2 times, most recently from 90beef6 to a44b7c9 Compare January 8, 2020 19:28
@MattKotsenas MattKotsenas force-pushed the feature/process-waitforexitasync branch from a44b7c9 to 740a615 Compare January 9, 2020 21:13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really doing what you want it do to, especially if you add the ThrowIfCancellationRequested I suggested earlier. I think this shouldn't use a CancellationToken at all, e.g.

Process p = CreateProcessLong();
p.Start();

Task[] tasks = (from i in Enumerable.Range(0, 10) select p.WaitForExitAsync()).ToArray();
Assert.All(tasks, t => !t.IsCompleted);
p.Kill();
Task.WaitAll(tasks);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologize, but I'm not following what it's doing versus should be doing. This test is basically an aync-ified clone of SingleProcess_TryWaitMultipleTimesBeforeCompleting.

Should the two tests diverge, or is there another test that should be added in addition?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The token passed to WaitForExitAsync is already canceled prior to the call. So it's going to get only to https://github.com/dotnet/runtime/pull/1278/files#diff-06c983bcaa17665febae824710b68dbeR1397 and never get any further. If the goal is to test, for example, the event handle hookup and unhookup, this won't do that. You would instead want to create a new CancellationTokenSource, pass its token to WaitForExitAsync, then cancel the token, and then await the previously returned task.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the additional info. I updated the unit test so that both cases are covered.

I also added comments to denote the different cases:

  • Token is already canceled, so return synchronous
  • Task is created, then token is canceled, so exercise cleanup code
  • Task is awaited, then process is killed (the happy path)
  • Waiting with a token on an already exited process, so return synchronous

Please let me know if there's anything else I missed. Thanks!

Per review feedback, change `WaitForExitAsync` to return a `Task`
instead of `Task<bool>`. Now, to determine if the process has exited,
callers should check `HasExited` after the await, and cancellation
follows the async pattern of setting canceled on the task.
Per code review feedback, remove the asserts that verify that the Task
returned by WaitForExitAsync is completed successfully / canceled, as
they're essentially Task tests and not relevant to WaitForExitAsync.
Per code review feedback, fix the
MultipleProcesses_ParallelStartKillWaitAsync test to make work a
`Func<Task>` instead of an `Action` since we're await-ing it.
Per code review feedback, converting ExitHandler from a local function
to an `EventHandler` to avoid the extra delegate allocation to convert
between the types.
Per code review, register the `TaskCompletionSource` with the
`CancellationToken` so that if an `OperationCanceledException` is thrown
the relevant token is available on
`OperationCanceledException.CancellationToken`.

To accomplish that the `TaskCompletionSourceWithCancellation` class
that's internal to `System.Net.Http` is moved to Common and consumed in
both places.

Unit tests are also updated to verify that the cancellation token passed
in is the same one returned from
`OperationCanceledException.CancellationToken`.
Per code review feedback, update `WaitForExitAsync` to not require the
user to call `EnableRaisingEvents` first.

Setting `EnableRaisingEvents` ourselves introduces the chance for an
exception in the case where the process has already exited, so I've
added a comment to the top of the method to detail the possible paths
through the code and added comment annotations for each case.

Lastly, I updated the unit tests to remove `EnableRaisingEvents` calls.
Per code review feedback, update the new unit tests in
ProcessWaitingTests to follow style guidelines.
Per code review feedback, update tests to follow coding guidelines,
simplify creating cancellation tokens, and verify synchronous completion
of tasks in the exited / canceled case.
Per code review feedback, add an early out for a non-exited process when
canceled.
Per code review feedback, add a test for a process that completes
normally and doesn't use a canellation token.
Per code review feedback, update the xmldocs for `WaitForExitAsync` to
specify the language keyword for "true", and add a `<returns>` element.
@MattKotsenas MattKotsenas force-pushed the feature/process-waitforexitasync branch from 76cef6d to 4555b12 Compare January 27, 2020 19:19
@MattKotsenas
Copy link
Member Author

Friendly ping to reviewers:

Is there any additional follow up need here? Or is this OK to merge?

@scalablecory
Copy link
Contributor

This looks good to me now. @stephentoub anything else?

@scalablecory
Copy link
Contributor

@ericstj who is area owner here?

@ericstj
Copy link
Member

ericstj commented Jan 27, 2020

@krwq but he's out on leave. I trust @stephentoub and @carlossanlop to review here.

Per code review feedback, update xmldocs to list other conditions that
can cause the function to return.
Per code review feedback, update the method guards to span multiple
lines to adhere to style guidelines.
Per code review feedback, update
SingleProcess_TryWaitAsyncMultipleTimesBeforeCompleting to test both the
case where the token is canceled before creating the Task as well as
after the task is already created.
@MattKotsenas MattKotsenas force-pushed the feature/process-waitforexitasync branch from aa4235f to 54a556a Compare February 1, 2020 03:50
@MattKotsenas
Copy link
Member Author

MattKotsenas commented Feb 10, 2020

Friendly ping to reviewers:

Is there any additional follow up need here? Or is this OK to merge?

@stephentoub
Copy link
Member

Thanks!

@stephentoub stephentoub merged commit ad7e1ff into dotnet:master Feb 13, 2020
@MattKotsenas MattKotsenas deleted the feature/process-waitforexitasync branch February 13, 2020 19:01
@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants