adamedx / scriptclass

Class definition extensions for PowerShell's object-based type system
Apache License 2.0
7 stars 1 forks source link

New-ScriptObject deadlock in ThreadJob cross-module scenarios #40

Open adamedx opened 6 days ago

adamedx commented 6 days ago

Describe the bug This issue reproduces only on PowerShell Core -- has not (yet) been reproduced on desktop.

To Reproduce Repro isn't 100% clear, but seems to be in cases where a thread created by module A uses Script-Object to create an object in module B:

  1. In outside of module M use Start-ThreadJob to create a new job with scriptblock that contains code that invokes the a static method on a scriptclass type defined outside of module M that uses New-ScriptObject to create and return a new object of a type hosted by module M
  2. Immediately use wait-job to wait for the job

The code will hang -- ctrl-c works to get out of it, but get-job will show the job is still running, and if you then try to exit the powershell session with the exit command it will hang. Given the latter behavior, while this is an issue with the scriptclass module, there is clearly something broken in PowerShell itself -- nothing we write using pure PowerShell should actually hard hang the shell.

I've been able to get the following repro on Windows powershell core (I've seen these hangs on Linux also, so should be the same there):

import-module scriptclass
import-module moduleM # A module that exposes a ScriptClass type TypeT

# As implied by the fact that we imported moduleM, the remaining code is not part of moduleM
scriptclass MyType { 
    static {
        function GetTypeInstance { new-so TypeT } # TypeT defined in moduleM
    }
}

# Types defined on this thread aren't visible in threads created by Start-ThreadJob, so you need
# to pass in the class object to make it accessible
$newJob = Start-ThreadJob { param($inputType) $inputType.GetTypeInstance() } -ArgumentList $::.MyType; write-host 'startedjob'; $newjob | wait-job
write-host "Successfully returned the type"

Expected behavior The code should return quickly with an instance of ScriptClass object type TypeB and the following output in the console:

startedjob
Successfully returned the type

Any subsequent invocations of get-job should show this job with a state Completed

Actual behavior is a hang -- the final message is never printed, so the output is simply the first line startedjob. You can break out of the hang with CTRL-C. However, get-job shows that the job is still running!

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
2      Job2            ThreadJob       Running       False           PowerShell           param($inputType) $inpu…

Additionally, if you then use exit to exit PowerShell altogether, that hangs! This last behavior indicates a bug in PowerShell itself.

Note that the bug does not seem to reproduce reliabily if you don't do the wait-job immediately after the start-threadjob, indicating a race condition of some sort.

I encountered this situation on a CI pipeline where it repro'd reliably when I added a very specific test. Rearranging the code made it seem to go away (at first I though it was related to making outgoing http requests in the other thread, even though that had worked reliably for years). Eventually I was able to isolate the behavior and come up with this very focused repro.

The workaround: don't use new-scriptobject in a threadjob (or maybe only use objects that are not exported from a module).