PowerShell / Polaris

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

PSReadLine holds on to thread that Polaris uses #136

Open TylerLeonhardt opened 6 years ago

TylerLeonhardt commented 6 years ago

On macOS, Polaris, (which uses Register-ObjectEvent) hangs until you hit a key in the terminal. Then it processes the request.

This looks suspiciously similar to the issue we saw in PowerShell Editor Services that @SeeminglyScience fixed.

I haven't tested on Windows, but I have a feeling this doesn't happen.

SeeminglyScience commented 6 years ago

Yep, same thing.

A couple things you could do:

  1. Override the same delegate we do in PSES. Keep in mind it's a "private" contract between PSES and PSRL, so I don't think that would be considered supported.

  2. Build LINQ expressions based on the AST of the script block (manually or via PSLambda). The event will likely be triggered on a different thread than either of the two threads that lock in this scenario.

  3. Accept that events can only be processed after key presses. If that's a feasible option at least temporarily, I'd recommend it. Hopefully there will be some fixes in the console API's down the road that solve the issue better than we can outside of corefx

TylerLeonhardt commented 6 years ago

Yeah Polaris running as a job works so 3 is at least fine for now....

TylerLeonhardt commented 6 years ago

Got some feedback on this today from a few people. Really should address this somehow... I don't know if I want to go down the "private" contract plan (although I appreciate @lzybkr for adding that). I wonder if I should stop relying on Register-ObjectEvent on unix and just hold on to the prompt and process events in a while loop.

Sucks to have to take away the prompt from the user, but this is what Node.js does today so I'm not too upset.

Tiberriver256 commented 6 years ago

We could offer two modes maybe?

Tiberriver256 commented 5 years ago

I opened https://github.com/PowerShell/PowerShell/issues/8478 to see if the PowerShell team has any suggestions or believes it to be a bug.

Tiberriver256 commented 5 years ago

Reopened in https://github.com/lzybkr/PSReadLine/issues/835 as it is a PSReadline issue not a PowerShell issue.

TylerLeonhardt commented 5 years ago

Thanks @Tiberriver256 😃

I think 2 modes makes sense.

1) default behavior 2) -ControlProcess or something to signify that Polaris will hold onto the pipeline thread until stopped

Tiberriver256 commented 5 years ago

Question from #194 via @mikeTWC1984:

Is there any news on PSReadline/*nix issue?

I'm looking for some work around that allows to use Polaris and keep PSReadline. I guess you mention this can be fixed by replacing Register-ObjectEvent whith a while loop. Is it an easy change? If so, can you show any sample code?

Btw, I noticed that this issue disappears if starting Polaris from VSCode Powershell Integrated console

Moved to keep discussion in one main thread

Tiberriver256 commented 5 years ago

There have been a few suggestions above and my guess as to why it works in VSCode is because of what SeeminglyScience has done to make it work (as referenced above).

@SeeminglyScience - Do you have the code handy somewhere you used to override the delegate in PSES?

SeeminglyScience commented 5 years ago

@Tiberriver256 Now that I think about it, the same route PSES takes isn't feasible here. PSRL only checks for the override at process start (or more specifically, it only checks the first time it calls ReadKey). In order to override it after that, you'd need to alter a field that isn't part of the private contract PSRL has with PSES.

Even if you did choose to do that, you'd still need to rip out the custom async version of ReadKey in PSES, and ship the native lib UnixConsoleEcho. All of that is a pretty hefty lift. I should also stress that it's not supported without getting the PS team to extend the PSRL/PSES private contract to Polaris.

Tbh I'd recommend putting some more attention on dotnet/corefx#25036 over reusing our hacky workarounds.

mikeTWC1984 commented 5 years ago

OK, thanks for the info. It doesn't sound like that ReadKey issues is anywhere close to resolution though. I was looking for a hack just because I want to use Polaris as a back-end for Jupiter like app, so need to keep PSReadline. One of the suggestion above was about running it as job, but didn't work. But it works if running in a separate runspace, so I'll stick to it for now. Other was to use while loop vs event, just not sure how to implement it. Wondering if switching to a binary module can fix that problem. I played with Universal Dashboard, it has similar feature to generate rest end points from script block. And it seem to work on Linux just fine (he seemed to use kestrel though).

mikeTWC1984 commented 5 years ago

I'll leave my workaround below, in case anybody also looking for options


function New-PolarisServer { param($port=8888)

    $ps = [powershell]::create()

    $info = $ps.AddScript({param($p)

      Remove-Module PSReadline
      New-PolarisGetRoute -Path "/" -Scriptblock { 
        $response.send(($PSVersionTable | Select-Object OS, PSEdition, Platform, PSVersion | ConvertTo-Json))
       }   
      Start-Polaris -port $p

    }).AddParameter("p", $port).Invoke()

    $AddRoute = { param([string]$route, [scriptblock]$sb, [string]$method="GET")
       $this.ps.Commands.Clear()
       $this.ps.AddCommand("New-PolarisRoute").AddParameters(@{
           Path=$route; ScriptBlock=$sb; Method=$method; Force=$true 
          }).Invoke()
    }

    $Dispose = { 
      $this.ps.Commands.Clear()
      $this.ps.AddCommand("Stop-Polaris").AddCommand("Clear-Polaris").Invoke()
      $this.ps.Dispose()
    }

    $obj = [PSCustomObject]@{ ps = $ps; info = $info }
    $obj | Add-Member -MemberType ScriptMethod -Name AddRoute -Value $AddRoute
    $obj | Add-Member -MemberType ScriptMethod -Name Dispose -Value $Dispose

    return $obj 
}

<# Usage

 $p = New-PolarisServer -port 8000
 $p.AddRoute("/hello", {$response.send("Hello there")} )
 iwr http://localhost:8000/hello
 $p.Dispose()
#>