Asynchronous handling / runspaces
Jonz opened this issue ยท 6 comments
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
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.
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
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.
@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:
- Write a wrapper subclass for
PSVariable
that is thread safe so multiple runspaces can interact with the samePSVariable
- Create a new runspace
- Copy every imported module to the new runspace
- Copy the variables (as the wrapper class) and functions of each module to the module in the new runspace
- Do the same for the global
SessionState
- Repeat for every new thread
That's the only way I can think of to add async without requiring the user to change anything.
I noticed Start-Polaris
still has -MinRunspaces
and -MaxRunspaces
parameters which should be removed as they are not used anymore.
Will ForEach-Object -Parallel be helpful for implementing this?