[Investigate] Fix PowerShell event handling · Issue #1591 · PowerShell/PowerShellEditorServices · GitHub
Skip to content

[Investigate] Fix PowerShell event handling #1591

Closed

Description

@rjmholt

From PowerShell/PSReadLine#1679 (comment).

PowerShell events when run in a non-nested shell try to phone home back to the runspace in which they were registered, with the event handler calling in from another thread.

The assumption PowerShell makes is that when an action occurs and we pulse the pipeline, if the pipeline isn't nested, it must be on a different thread.

Of course in our case, the pipeline is on our thread and isn't nested, because we're running it under an application. This seems to be an oversight in the PowerShell hosting model and I think it needs to be addressed in PowerShell; any attempt to run a PowerShell pipeline on a thread not dedicated to it will have this issue.

Unfortunately for us though, this is a behaviour we're stuck with in Windows PowerShell and PS 7.0, 7.1 and 7.2, so we have to work out what mitigation we can provide.

There are a couple of possible solutions:

  1. We can construct an artificial internal cmdlet as our "top level" pipeline and put all of our actual execution underneath it. This would be relatively simple and would probably only have a bit of overhead, but would mean our pipelines are always nested, which could possibly have side-effects.
  2. We could experiment with using reflection to just lie to PowerShell about our top-level pipeline being nested. This would probably hit errors somewhere, but might be worth a try.
  3. The PSLocalEventManager class is an internal extension of a public abstract class and most of its methods are virtual. We might conceivably be able to generate an override to this class and set it internally in the engine to redirect the eventing flow back to us. I'm not sure if this is possible, and if it is it would probably be a fair amount of work. But it would be the most "integrated" approach.
  4. Abandon the event loop on the runspace thread and instead use it as a pump for a dedicated runspace thread. This would mean we can no longer run delegates on the runspace thread as we currently do (including at startup, when we implicitly run all our code on the runspace thread so that things like PSReadLine work correctly). We could address this by changing the delegate task implementation to use another internal cmdlet that simply executes the given delegate in its EndProcessing() block. But this would require some reimplementation inside the internal host.

It's also worth noting that, as far as we can tell, the ISE doesn't have any awareness of this scenario either. So it's not clear if this is a quiet bug in the ISE that either hasn't been noticed or hasn't been large enough to come to attention.

For now, I think, event handling in PSES isn't important enough to invalidate the work here and this is something I'm hopeful can be fixed later.

Activity

  1. ghost added
    Needs: TriageMaintainer attention needed!
    on Oct 21, 2021
  2. andyleejordan commented on Oct 21, 2021

    @andyleejordan
    Member

    It's also worth noting that, as far as we can tell, the ISE doesn't have any awareness of this scenario either. So it's not clear if this is a quiet bug in the ISE that either hasn't been noticed or hasn't been large enough to come to attention.

    For now, I think, event handling in PSES isn't important enough to invalidate the work here and this is something I'm hopeful can be fixed later.

    I agree with this. I think we can move forward with the caveat that the integrated terminal does not support PowerShell or .NET events (or at least, not well). Technically the integrated terminal isn't a fully-fledged PowerShell process, it's the integration point between the editor and editor services that just so happens to let the user run code in the runspace hosting the editor's services. I know it doesn't quite look like that to end user, but it is what it is, and presumably the ISE is somewhat similar.

  3. andyleejordan commented on Oct 21, 2021

    @andyleejordan
    Member

    Is there a way we could, like, turn off PowerShell events? Throw an error if one tries to be registered? Or would that involve having to do one of the four proposed workarounds?

  4. rjmholt commented on Oct 21, 2021

    @rjmholt
    ContributorAuthor

    So the underlying event manager is directly available with $ExecutionContext.Events. We'd need to do (3) to override it.

    Normally thought, Register-EngineEvent and Register-ObjectEvent would be what most people would use, and we could conceivably override those cmdlets to do something else with them...

  5. rjmholt commented on Oct 21, 2021

    @rjmholt
    ContributorAuthor
  6. andyleejordan commented on Oct 21, 2021

    @andyleejordan
    Member

    This seems like a problem where we could get 80% of it working (most use cases, like those two cmdlets) pretty easily (override them to queue the events on our thing), and it's just the last 20% (making all events including .NET-esque ones) is what would be very hard.

  7. daxian-dbw commented on Oct 27, 2021

    @daxian-dbw
    Member

    Quoted from PowerShell/PSReadLine#1679 (comment):

    In order for PowerShell eventing to work with the dedicated pipeline thread design in PSES, PowerShell needs to allow disabling the pulse pipeline generation and OnIdle events.

    I think we should do this in PowerShell engine. Other events can still be generated, and it's up to the hosting application to spin up pulse pipeline to allow event handling. Also, it's up to the hosting application to define when it is idle, and generate OnIdle event in such case.

  8. andyleejordan commented on Oct 27, 2021

    @andyleejordan
    Member

    I think that'd be a great change, and it would fix this for future versions of PowerShell Core, but sadly it'll remain quite broken for Windows PowerShell 😫

  9. daxian-dbw commented on Oct 27, 2021

    @daxian-dbw
    Member

    Also, we (maybe me) need to go back review the ISE code closely to understand how it does things, for example, is it using a dedicated pipeline thread? how does it handle the PS eventing? It doesn't need to deal with PSReadLine which makes it a lot simpler, but still good to know how things work with it.

  10. 15 remaining items

  11. changed the title [-][Revamp pipeline thread handling] Fix PowerShell event handling[/-] [+][Investigate] [Revamp pipeline thread handling] Fix PowerShell event handling[/+] on May 9, 2022
  12. changed the title [-][Investigate] [Revamp pipeline thread handling] Fix PowerShell event handling[/-] [+][Investigate] Fix PowerShell event handling[/+] on May 9, 2022
  13. SeeminglyScience commented on May 9, 2022

    @SeeminglyScience
    Collaborator

    Does anyone remember what specifically they used to repro this? Seems to be working for me, it's possible I accidentally fixed this while getting attach to process working

  14. daxian-dbw commented on May 10, 2022

    @daxian-dbw
    Member

    I remember the repro is to register an event subscriber to the Engine.Idle event, in the integrated console.

  15. SeeminglyScience commented on May 10, 2022

    @SeeminglyScience
    Collaborator

    Then I think we're good 🎉

    image

  16. SeeminglyScience commented on May 10, 2022

    @SeeminglyScience
    Collaborator

    FWIW I think the issue was here:

    https://cs.github.com/PowerShell/PowerShellEditorServices/blob/dd870ec1887c3cd69f18459d3e226067ab4f2098/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs#L940

    That just creates a instance of PowerShell and assigns the runspace directly. What needed to happen was PowerShell.Create(RunspaceMode.CurrentRunspace) needed to be called instead to mark it as nested. That might have thrown at that point in initialization, but setting Runspace.DefaultRunspace would fix that I believe.

    Part of the changes I made for attach to process is creating a new PowerShell instance for every context frame rather than reusing the old one. When I did that, since I was using CurrentRunspace, it's now marked as nested.

    I'm gonna go ahead and close this

  17. Repository owner moved this from Todo to Done in American Pharoah on May 10, 2022
  18. daxian-dbw commented on May 10, 2022

    @daxian-dbw
    Member

    Patrick Meinecke (@SeeminglyScience) As far as I can remember, it was because sometimes there was not running pipeline in the default session, and when an event was triggered in that case, the event manager created a pulse pipeline. However, while that pulse pipeline was running, something happens in the extension that causes a task to be queued and executed. Since they both use the same Runsapce at that point, the "a pipeline already running" exception will be thrown.
    Is it now guaranteed that there is always a running pipeline within the default session of the integrated console?

  19. SeeminglyScience commented on May 10, 2022

    @SeeminglyScience
    Collaborator

    So I think what was happening was we create an instance of PowerShell that is not marked as Nested. Then in our OnIdle delegate, we were calling PsesInternalHost.InvokePSCommand("0") to trigger event handling.

    But since we were doing that while the pipeline thread was technically busy, we needed the PowerShell instance to be marked Nested so it knows we're already on the right thread. So it knows we're the one making it busy. Since it wasn't marked Nested, it just threw every time we tried to trigger PowerShell event processing.

  20. daxian-dbw commented on May 10, 2022

    @daxian-dbw
    Member

    we needed the PowerShell instance to be marked Nested so it knows we're already on the right thread

    That explains it. This is great! I thought we would have to fix how event manager firing pulse pipeline in PowerShell, but this resolves the problem perfectly!

  21. andyleejordan commented on May 10, 2022

    @andyleejordan
    Member

    I swear Rob and I both tried to get it marked Nested properly and ran into issues. IDK what you did, but you did good!

  22. daxian-dbw commented on May 10, 2022

    @daxian-dbw
    Member

    Patrick Meinecke (@SeeminglyScience) Would be good to talk about how you did it (and maybe what was done wrong before) in the team meeting this week 😄

  23. SeeminglyScience commented on May 11, 2022

    @SeeminglyScience
    Collaborator

    I swear Rob and I both tried to get it marked Nested properly and ran into issues. IDK what you did, but you did good!

    I would guess that y'all did try PowerShell.Create(RunspaceMode.CurrentRunspace) but hit this error here due to how early it was being called. If so, that could have been fixed with:

    var oldRunspace = Runspace.DefaultRunspace;
    try
    {
        Runspace.DefaultRunspace = theOneWeJustMade;
        return PowerShell.Create(RunspaceMode.CurrentRunspace);
    }
    finally
    {
        Runspace.DefaultRunspace = oldRunspace;
    }

    Patrick Meinecke (@SeeminglyScience) Would be good to talk about how you did it (and maybe what was done wrong before) in the team meeting this week 😄

    That's a good idea but I'll have to see if I can find a way to present the information in a way that's useful. So next week! 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions