PowerShell / Polaris

A cross-platform, minimalist web framework for PowerShell
https://powershell.github.io/Polaris/
MIT License
508 stars 113 forks source link

Asynchronous handling / runspaces #127

Open Jonz opened 5 years ago

Jonz commented 5 years ago

Hello,

Firstly, great work - very powerful yet easy to understand.

I suppose this might be more of a question than an issue, but I noticed that when doing long-running tasks I'm unable to run anything until the first process finishes. I wanted to check whether this is by design, but looks like it might've worked as part of the runspace functionality? I noted that the last commit was WIP: Replacing PowerShell Runspaces with Register-EventObject for Asynchronous Handling - so that adds up.

More details: In the example.ps1 I noticed that the Min/Max runspaces are set but as far as I can tell, unused in the current version. e.g. in example.ps1:

$app = Start-Polaris -Port 8082 -MinRunspaces 1 -MaxRunspaces 5

I had a brief look to see if I could figure it out but it's way over my head. Any help appreciated.

Jon

TylerLeonhardt commented 5 years ago

You're correct - since we are using Register-ObjectEvent instead of PowerShell runspaces, only 1 request can be handled at a time.

Managing a runspace pool (and doing it in C#) was too much overhead. I wanted to keep it simple so that people who know PowerShell could use it effectively and contribute to it effectively.

I'm thinking about how we can be better about this by handing multiple requests but unfortunately PowerShell doesn't have the greatest asynchronous abilities...... yet.

jimmylevell commented 5 years ago

Hello,

firstly I really enjoy this project and your great work! It is for us a great opportunity to provide REST-APIs for a lot of systems which only have a powershell API. Because these systems are not as fast in processing an asynchronous handling is really appropriate. So that polaris can still serve other requests.

Do you yet have a plan to add this feature to the roadmap?

Kind regards James

TylerLeonhardt commented 5 years ago

We get requests for this quite a bit. It's tough to balance simplicity of the project and feature richness in this case.

Ideally, I want to keep the same experience today while enabling this feature. That means that we need to handle scope really carefully.

For example, today this works:

$foo = "bar"

New-PolarisGetRoute 'foo' {
    $Response.Send($foo) # will send "bar"
}

In the previous implementation of Polaris that used a runspace pool, that was not the case.

@BrucePay and @SeeminglyScience are two people I know who have been able to successfully pull this off multi-threaded. I'll need to speak to them about how they've managed to pull in variables and functions defined out of the script block in to the execution.

SeeminglyScience commented 5 years ago

@tylerl0706 variables are pretty simple, all you need to do is save the result of Get-Variable. Well, that's all I needed to do, but if you want to capture variables created after New-PolarisGetRoute runs initially, then it gets a lot tricker. Here's an example of what I mean:

$foo = "bar"

New-PolarisGetRoute 'foo' {
    $Response.Send($foo + $foo2) # will send "barbar2"
}

$foo2 = "bar2"

If you do it the way I did it then the route won't know about $foo2. You'd probably also want to make a thread safe version of PSVariable.

Then there's functions which PSLambda doesn't need to worry about because it can't invoke commands. Functions (and all script blocks) have affinity to the runspace they were created in. So if you try to run a function in a different runspace, it will try to route it back to the original runspace. You can strip the affinity, but then you lose all state (and you would basically be doing the same thing as the original RunspacePool model).

I don't think it's feasible to add async without changing either convention (adding something like $using:var support) or losing scoped items (variables/functions). That said, here's my rough guess at what you would have to do:

  1. Write a wrapper subclass for PSVariable that is thread safe so multiple runspaces can interact with the same PSVariable
  2. Create a new runspace
  3. Copy every imported module to the new runspace
  4. Copy the variables (as the wrapper class) and functions of each module to the module in the new runspace
  5. Do the same for the global SessionState
  6. Repeat for every new thread

That's the only way I can think of to add async without requiring the user to change anything.

bgelens commented 5 years ago

I noticed Start-Polaris still has -MinRunspaces and -MaxRunspaces parameters which should be removed as they are not used anymore.

ili101 commented 4 years ago

Will ForEach-Object -Parallel be helpful for implementing this?