GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Fix BeginInvoke/EndInvoke to return results when Stop or BeginStop/EndStop was called previously by daxian-dbw · Pull Request #7917 · PowerShell/PowerShell

Conversation

@daxian-dbw

@daxian-dbw Dongbo Wang (daxian-dbw) commented Oct 1, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

PR Summary

Fix BeginInvoke()/EndInvoke(IAsyncResult) to return results when Stop() or BeginStop(callback, state)/EndStop(IAsyncResult) was called previously.

Without this fix, EndInvoke(IAsyncResult) returns null when it should return a collection of results:

PS:1> $ps = [powershell]::Create()
PS:2> $ps.AddScript("'Hello'; Sleep 2; 'World'") > $null
PS:3> $a = $ps.BeginInvoke(); $ps.Stop()
PS:4> $a = $ps.BeginInvoke(); $m = $ps.EndInvoke($a)
PS:5> $m -eq $null
True

The root cause is that OutputBuffer is not set to null in Stop and BeginStop/EndStop when the powershell instance owns the OutputBuffer object. Since the pipeline has been intentionally stopped by the caller, the OutputBuffer should be set to null as well when it's owned by the PowerShell instance.

PR Checklist

  • PR has a meaningful title
    • Use the present tense and imperative mood when describing your changes
  • Summarized changes
  • Change is not breaking
  • Make sure all .h, .cpp, .cs, .ps1 and .psm1 files have the correct copyright header
  • This PR is ready to merge and is not Work in Progress.
    • If the PR is work in progress, please add the prefix WIP: to the beginning of the title and remove the prefix when the PR is ready.
  • User-facing changes
    • Not Applicable
    • OR
    • User-facing Documentation needed
      • Issue filed - Issue link:
  • Testing - New and feature
    • Not Applicable or can only be tested interactively
    • OR
    • Make sure you've added a new test if existing tests do not effectively test the code changed
      • Add [feature] if the change is significant or affects feature tests

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

PixleProgrambetaxl reacted with hooray emoji
All reactions
  • 🎉 1 reaction
@daxian-dbw
Fix BeginInvoke/EndInvoke to return results when Stop or BeginStop/En…
1954fef
…dStop was called previously
@daxian-dbw
Dongbo Wang (daxian-dbw) requested a review from Bruce Payette (BrucePay) October 1, 2018 23:30
@daxian-dbw
[Feature] Add CI tag
cf9f0b8
@daxian-dbw Dongbo Wang (daxian-dbw) self-assigned this Oct 1, 2018
iSazonov
Ilya (iSazonov) reviewed Oct 2, 2018
View reviewed changes
Comment thread test/powershell/engine/Api/BasicEngine.Tests.ps1 Outdated
Describe "EndInvoke() should return a collection of results" -Tags "CI" {
BeforeAll {
$ps = [powershell]::Create()
$ps.AddScript("'Hello'; Sleep 1; 'World'") > $null

@iSazonov Ilya (iSazonov) Oct 2, 2018

Copy link
Copy Markdown
Collaborator

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.

Sleep 1

We should use Wait-UntilTrue and use flags to sync threads.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@anmenaga Andrew (anmenaga) Oct 10, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

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.

Dongbo Wang (@daxian-dbw) I also don't like sleep timeouts-based synchronization in tests.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@daxian-dbw Dongbo Wang (daxian-dbw) Oct 10, 2018

Copy link
Copy Markdown
Member Author

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.

Thank you both for the comments. The Sleep 1 is not really necessary, so I removed it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@daxian-dbw
Address code review comment
86c164b
rjmholt
Rob Holt (rjmholt) approved these changes Oct 10, 2018
View reviewed changes

@rjmholt Rob Holt (rjmholt) left a comment

Copy link
Copy Markdown
Collaborator

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.

LGTM, but have left a comment as well

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/System.Management.Automation/engine/hostifaces/PowerShell.cs
}

// PowerShell no longer owns the output buffer when it is passed back to the caller.
ResetOutputBufferAsNeeded();

@rjmholt Rob Holt (rjmholt) Oct 10, 2018

Copy link
Copy Markdown
Collaborator

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.

Would it be better to have this repeated call in a finally block? Or is there a scenario where an error occurs and we don't want to do it?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@daxian-dbw Dongbo Wang (daxian-dbw) Oct 10, 2018

Copy link
Copy Markdown
Member Author

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 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 NewArgumentException is thrown from 4 lines above, maybe we don't want to reset the output? I'm inclined to keep it as is given the original code is not in a finally block and we haven't yet heard any issues regarding that.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

rjmholt reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@rjmholt Rob Holt (rjmholt) Oct 10, 2018

Copy link
Copy Markdown
Collaborator

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.

Okie doke -- thought I'd comment since we call it at the end of 3/4 branches of the code.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

daxian-dbw reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
PaulHigin
Paul Higinbotham (PaulHigin) suggested changes Oct 10, 2018
View reviewed changes
Comment thread src/System.Management.Automation/engine/hostifaces/PowerShell.cs
@@ -3405,13 +3405,8 @@ public PSDataCollection<PSObject> EndInvoke(IAsyncResult asyncResult)
psAsyncResult.EndInvoke();

@PaulHigin Paul Higinbotham (PaulHigin) Oct 10, 2018

Copy link
Copy Markdown
Contributor

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.

psAsyncResult.EndInvoke();              // Stop throws System.Management.Automation.PipelineStoppedException

I think we should catch the pipeline stopped exception and ensure EndInvoke() always releases the output buffer.

try
{
    psAsyncResult.EndInvoke();
}
catch (System.Management.Automation.PipelineStoppedException) { }
finally
{
    if (OutputBuffer) { ... }
}

...
return psAsyncResult.Output

Then the pattern is:

$ps = [powershell]::Create()
$ps.AddScript('1..10 | % { sleep 1; "Output $_" }')
$async = $ps.BeginInvoke()
sleep 3
$ps.Stop()
$output = $ps.EndInvoke($async)
$async = $ps.BeginInvoke()
...

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

rjmholt reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@daxian-dbw Dongbo Wang (daxian-dbw) Oct 10, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

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 Paul Higinbotham (@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:

$ps.Commands.Clear()
$ps.AddScript("'Hello World'; throw blah") > $null
$a=$ps.BeginInvoke()
$b = $ps.EndInvoke($a)
$b
> Hello World
$ps.AddScript("'Hello World'; [System.IO.FileInfo]::new(`$null)") > $null
$a = $ps.BeginInvoke()
$b = $ps.EndInvoke($a)
$b
> Hello World

As for the PipelineStoppedException thrown from EndInvoke due to $ps.Stop() being called previously, the cleanup is not needed as it's already done in Stop(), and we have to do the cleanup in Stop because with the current behavior, a developer will never call $ps.EndInvoke after calling $ps.Stop because 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 the Stop method. The discussion of this would be a separate issue.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@iSazonov Ilya (iSazonov) Oct 11, 2018

Copy link
Copy Markdown
Collaborator

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.

If we really want the user be able to get the partial results when $ps.Stop() is called,

I think it could be in EndInvoke() as option (and perhaps timeout option too).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@PaulHigin Paul Higinbotham (PaulHigin) Oct 11, 2018

Copy link
Copy Markdown
Contributor

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.

Dongbo Wang (@daxian-dbw) You have an error in your example script.

PS C:\> $ps = [powershell]::Create()
PS C:\> $ps.AddScript('"Hello!"; throw "blah"') > $null
PS C:\> $a = $ps.BeginInvoke()
PS C:\> $b = $ps.EndInvoke($a)
Exception calling "EndInvoke" with "1" argument(s): "blah"
At line:1 char:1
+ $b = $ps.EndInvoke($a)
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : RuntimeException

PS C:\> $b
PS C:\>

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:

  1. Catch pipeline stop exception and insert into Error stream as non-terminating error. But this doesn't address a general terminating exception.
  2. Catch all terminating exceptions (in EndInvoke) and insert into Error stream, marking the error as terminating in some way.
  3. Create a new "EndInvoke" exception class that is thrown, which wraps the actual terminating error and also provides the output buffer as a parameter.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@daxian-dbw Dongbo Wang (daxian-dbw) Oct 11, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

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.

You have an error in your example script.

Good catch. Yes, I see the problem with throw now. 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).

I feel that updating PowerShell.Stop() to throw away the output buffer only compounds this problem.

Base on the code below

PowerShell/src/System.Management.Automation/engine/hostifaces/PowerShell.cs

Lines 2890 to 2898 in 680ad9c

OutputBuffer = new PSDataCollection<PSObject>();
OutputBufferOwner = true;
if (_isBatching || ExtraCommands.Count != 0)
{
return BeginBatchInvoke<T, PSObject>(input, OutputBuffer, settings, callback, state);
}
return CoreInvokeAsync<T, PSObject>(input, OutputBuffer, settings, callback, state, OutputBuffer);

it looks to me setting OutputBuffer = null does not throw away the output buffer. The PSDataCollection object referenced by OutputBuffer gets passed to CoreInvokeAsync via the parameters output and asyncResultOutput, then output is used by the Worker as the output stream and asyncResultOutput is assigned to PowerShellAsyncResult.Output, which will be used to return the results from EndInvoke.

So, even though OutputBuffer is set to null in Stop() and EndStop, it won't affect the output stream used by the worker, and theoretically the partial results are still available in psAsyncResult.Output when getting to EndInvoke.

while breaking the existing contract of BeginInvoke/EndInvoke pattern.

Having a pair of BeginXXX and EndXXX is the pattern only when you want the execution to complete, either to success or failure.

However, when Stop is called after BeginInvok, I don't think it's a must-have for the user to call EndInvoke(), because

  1. that's not something the user can do today and we won't be able to change their existing habit;
  2. there is a good chance that the user just doesn't care about the execution anymore after calling Stop;
  3. it would be a breaking change if we suddenly tell the user you have to call EndInvok again after Stop to clean up some state.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@PaulHigin Paul Higinbotham (PaulHigin) Oct 11, 2018

Copy link
Copy Markdown
Contributor

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@PaulHigin Paul Higinbotham (PaulHigin) Oct 11, 2018

Copy link
Copy Markdown
Contributor

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)

Offline Dongbo Wang (@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!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@daxian-dbw Dongbo Wang (daxian-dbw) Oct 11, 2018

Copy link
Copy Markdown
Member Author

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 Paul Higinbotham (@PaulHigin)'s concern on the behavior of PowerShell.EndInvoke.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
PaulHigin
Paul Higinbotham (PaulHigin) approved these changes Oct 11, 2018
View reviewed changes

@PaulHigin Paul Higinbotham (PaulHigin) left a comment

Copy link
Copy Markdown
Contributor

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.

LGTM. But please create EndInvoke() losing data issue to track separately.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@daxian-dbw Dongbo Wang (daxian-dbw) mentioned this pull request Oct 11, 2018
PowerShell.EndInvoke(IAsyncResult) doesn't provide a way for users to get the partial results when a terminating error is thrown #8005
Closed
@daxian-dbw
Dongbo Wang (daxian-dbw) merged commit 9b94f18 into PowerShell:master Oct 12, 2018
@daxian-dbw
Dongbo Wang (daxian-dbw) deleted the endinvoke branch October 12, 2018 17:47
Aditya Patwardhan (adityapatwardhan) pushed a commit to adityapatwardhan/PowerShell that referenced this pull request Apr 9, 2019
@daxian-dbw
Fix BeginInvoke/EndInvoke to return results when Stop or BeginStop/En…
e37be6e
…dStop was called previously (PowerShell#7917)

The root cause is that `OutputBuffer` is not cleaned up (set to `null`) in `Stop` and `BeginStop/EndStop` when the powershell instance owns the `OutputBuffer` object. Since the pipeline has been intentionally stopped by the caller, the `OutputBuffer` should be set to `null` as well when it's owned by the PowerShell instance, just like how it's cleaned up in `EndInvoke`.
Thatgfsj (Thatgfsj) pushed a commit to Thatgfsj-contribs/PowerShell that referenced this pull request Aug 6, 2026
@daxian-dbw
Fix BeginInvoke/EndInvoke to return results when Stop or BeginStop/En…
d9081e5
…dStop was called previously (PowerShell#7917)

The root cause is that `OutputBuffer` is not cleaned up (set to `null`) in `Stop` and `BeginStop/EndStop` when the powershell instance owns the `OutputBuffer` object. Since the pipeline has been intentionally stopped by the caller, the `OutputBuffer` should be set to `null` as well when it's owned by the PowerShell instance, just like how it's cleaned up in `EndInvoke`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@iSazonov Ilya (iSazonov) iSazonov left review comments
@BrucePay Bruce Payette (BrucePay) Awaiting requested review from BrucePay
+3 more reviewers
@rjmholt Rob Holt (rjmholt) rjmholt approved these changes
@anmenaga Andrew (anmenaga) anmenaga left review comments
@PaulHigin Paul Higinbotham (PaulHigin) PaulHigin approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@daxian-dbw Dongbo Wang (daxian-dbw)

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@daxian-dbw @rjmholt @anmenaga @PaulHigin @iSazonov
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.