-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Fix BeginInvoke/EndInvoke to return results when Stop or BeginStop/EndStop was called previously #7917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix BeginInvoke/EndInvoke to return results when Stop or BeginStop/EndStop was called previously #7917
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3405,13 +3405,8 @@ public PSDataCollection<PSObject> EndInvoke(IAsyncResult asyncResult) | |
| psAsyncResult.EndInvoke(); | ||
| EndInvokeAsyncResult = null; | ||
|
|
||
| if (OutputBufferOwner) | ||
| { | ||
| // PowerShell no longer owns the output buffer when it is passed back to the caller. | ||
| OutputBufferOwner = false; | ||
| OutputBuffer = null; | ||
| } | ||
|
|
||
| // PowerShell no longer owns the output buffer when it is passed back to the caller. | ||
| ResetOutputBufferAsNeeded(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be better to have this repeated call in a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good question and I'm not really sure if we should put it in a finally block. Don't know about other exceptions, but if the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okie doke -- thought I'd comment since we call it at the end of 3/4 branches of the code. |
||
| return psAsyncResult.Output; | ||
| } | ||
| catch (InvalidRunspacePoolStateException exception) | ||
|
|
@@ -3442,6 +3437,9 @@ public void Stop() | |
| IAsyncResult asyncResult = CoreStop(true, null, null); | ||
| // This is a sync call..Wait for the stop operation to complete. | ||
| asyncResult.AsyncWaitHandle.WaitOne(); | ||
|
|
||
| // PowerShell no longer owns the output buffer when the pipeline is stopped by caller. | ||
| ResetOutputBufferAsNeeded(); | ||
| } | ||
| catch (ObjectDisposedException) | ||
| { | ||
|
|
@@ -3503,6 +3501,9 @@ public void EndStop(IAsyncResult asyncResult) | |
| } | ||
|
|
||
| psAsyncResult.EndInvoke(); | ||
|
|
||
| // PowerShell no longer owns the output buffer when the pipeline is stopped by caller. | ||
| ResetOutputBufferAsNeeded(); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
@@ -3560,6 +3561,18 @@ public void Dispose() | |
| /// </summary> | ||
| internal PSDataCollection<PSObject> OutputBuffer { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Reset the output buffer to null if it's owned by the current powershell instance. | ||
| /// </summary> | ||
| private void ResetOutputBufferAsNeeded() | ||
| { | ||
| if (OutputBufferOwner) | ||
| { | ||
| OutputBufferOwner = false; | ||
| OutputBuffer = null; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// This has been added as a work around for Windows8 bug 803461. | ||
| /// It should be used only for the PSJobProxy API. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scenario seems to be completely broken, however I don't think this is the right solution. My understanding is that the pattern is that EndInvoke is always called after BeginInvoke, and the output buffer is not released until EndInvoke is called. However, this line will throw a "pipeline stopped" exception, and the output buffer is never released.
I think we should catch the pipeline stopped exception and ensure EndInvoke() always releases the output buffer.
Then the pattern is:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked to @PaulHigin offline and the concern is more like "if an terminating exception is thrown from
$ps.EndInvoke, there should be a way to get the partial output". However, I found that's already the case now:As for the
PipelineStoppedExceptionthrown fromEndInvokedue to$ps.Stop()being called previously, the cleanup is not needed as it's already done inStop(), and we have to do the cleanup inStopbecause with the current behavior, a developer will never call$ps.EndInvokeafter calling$ps.Stopbecause that will raise an exception.If we really want the user be able to get the partial results when
$ps.Stop()is called, then the partial results probably should be returned somehow from theStopmethod. The discussion of this would be a separate issue.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could be in EndInvoke() as option (and perhaps timeout option too).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daxian-dbw You have an error in your example script.
I feel this is fundamentally broken because if a terminating error occurs (including stop) then any data generated up to that point is lost ... a major sin in my book.
I feel that updating PowerShell.Stop() to throw away the output buffer only compounds this problem, while breaking the existing contract of BeginInvoke/EndInvoke pattern.
Some ideas to remedy:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Yes, I see the problem with
thrownow. It's weird though[System.IO.FileInfo]::new($null)works as you expected in my second example, which makes it so inconsistent. Please feel free to open an issue for your concern on this behavior (along with the ideas for the potential fix).Base on the code below
PowerShell/src/System.Management.Automation/engine/hostifaces/PowerShell.cs
Lines 2890 to 2898 in 680ad9c
it looks to me setting
OutputBuffer = nulldoes not throw away the output buffer. ThePSDataCollectionobject referenced byOutputBuffergets passed toCoreInvokeAsyncvia the parametersoutputandasyncResultOutput, thenoutputis used by theWorkeras the output stream andasyncResultOutputis assigned toPowerShellAsyncResult.Output, which will be used to return the results fromEndInvoke.So, even though
OutputBufferis set tonullinStop()andEndStop, it won't affect the output stream used by the worker, and theoretically the partial results are still available inpsAsyncResult.Outputwhen getting toEndInvoke.Having a pair of
BeginXXXandEndXXXis the pattern only when you want the execution to complete, either to success or failure.However, when
Stopis called afterBeginInvok, I don't think it's a must-have for the user to callEndInvoke(), becauseStop;EndInvokagain afterStopto clean up some state.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern is that calling PowerShell.Stop() should not lose data, and the change you suggest does this (let me know if I am incorrect here). I admit that EndInvoke() is also broken and loses data but I feel we should fix this rather than compound the problem. I also respectfully disagree that Stop() should work the same as EndInvoke(). My reading of the code is that that was not the intention. Perhaps we should submit this to the committee to get their view.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Offline @daxian-dbw Dongbo showed me that this is in fact incorrect (I didn't look at the code closely enough).
The issue of EndInvoke() losing data can be treated separately and Dongbo will create an issue to track it. I have no objection to this change.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#8005 is opened to track @PaulHigin's concern on the behavior of
PowerShell.EndInvoke.