Deadlocking interop
See original GitHub issueGreetings. As it happens, I’m wrapping the WinRM API. Unfortunately I’ve run into a road-block.
The API has 1) Begin-/End-Invoke, 2) (ps : PowerShell).Streams.Error.{DataAdding,Completed}. I’m trying to join the EndInvoke with the Completed event of the Error stream, but for some number of runs; about half of them; everything freezes up.
I have code like this:
// suspicious function
let streamOf n (psdc : PSDataCollection<_>) =
let mb = Mailbox ()
let stop = IVar ()
psdc.DataAdding.Add <| fun args ->
printDataAdding n args
mb *<<+ args |> start
psdc.Completed.Add <| fun args ->
printCompleted n args
stop *<= () |> start
Stream.indefinitely mb
|> Stream.takeUntil stop
// a little later in the file:
job {
try
let outputs = subscribeStreams ps returned
let! res = Job.fromBeginEnd (beginInvoke ps) ps.EndInvoke
if ps.HadErrors then
printfn "PowerShell had errors; draining stream"
let! errors =
outputs.error
|> Stream.foldFromBack (Job.result [])
(fun s t -> s |> Job.map (fun s -> t :: s))
do! rI *<= Choice.createSnd errors
else
printfn "PowerShell succeeded; draining stream"
let objects =
Seq.foldBack (fun t s -> t :: s) res []
do! rI *<= Choice.create objects
with e ->
//printfn "in job, writing transport exception"
do! rI *<=! e
}
// tested with
node
|> WindowsNode.runCommand ("Write-Error", ["We know, we know"])
|> run
// trace output
WindowsNode: (cb) availability: Busy
PowerShell: E.DataAdding(
PowerShellInstanceId=9c113bd1-34dc-48e3-9b0a-648deecab88f,
ItemAdded=We know, we know
)
WindowsNode: (cb) availability: Available
PowerShell had errors; draining stream
// nothing happens here
In short, the draining of the stream depends on
psdc.Completed.Add <| fun args ->
printCompleted n args
stop *<= () |> start
Firing before the job scheduler blocks on the Stream fold; otherwise the stream is never terminated and cannot be folded. However, despite only having a single run, at the topmost level (albeit with desktop GC), it would seem that is never fired.
It can be circumvented by passing a p: Promise<unit> to make it Stream.takeUntil (stop <|> p) and then giving the promise a unit value after EndInvoke has returned; but then in about half of the cases, the output is never fully captured.
Is this a scheduler issue?
Issue Analytics
- State:
- Created 7 years ago
- Comments:15 (11 by maintainers)

Top Related StackOverflow Question
Wow, my man. Well spotted. Resolving that cycle made it work. I wonder why the error stream doesn’t close until Dispose is called; presumably the error output would be contained in time for a given invocation in PS…
The
foldBackandfoldFromBackstream combinators are lazy and, depending on the given combiner function, they do not necessarily consume the whole stream. For example, you could write a function to extract the head of a stream like this:The above stream combinator,
head, only examines at most the firstConsof the given streamxs.In this case,
the computation proceeds in such a way that the entire stream is first deconstructed and a sequence of
Job.mapoperations is built and finally the sequence of operations is executed. It would likely be faster to use an ordinary fold and list reversal.However, in retrospect, the laziness of
foldFromBackcould not possibly have been a cause of the problem in this case.