microsoft/fsharplu

`startProcessAsync` deadlocks if process exists before calling AwaitEvent

Closed this issue · 0 comments

blumu commented

With the NoTimeout option, startProcessAsync can deadlock when waiting for the process to terminate if the process exits before the call to Async.AwaitEvent:

https://github.com/Microsoft/fsharplu/blob/df50dee9590902a8bb4a15277f22c18711ac2207/FSharpLu/Diagnostics.fs#L136

Repro (extracting minimal code from Diagnostics.fs)

let deadlockRepro =
    async {
        use instance =
            new System.Diagnostics.Process(
                StartInfo =
                    System.Diagnostics.ProcessStartInfo
                        (
                            FileName = "notepad.exe",
                            WorkingDirectory = ".",
                            Arguments = "",
                            CreateNoWindow = false,
                            UseShellExecute = false,
                            RedirectStandardOutput = false,
                            WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
                            Verb =   "runas"
                        ),
                EnableRaisingEvents = true)

        let r = instance.Start()
        printfn "killing the process"
        System.Diagnostics.Process.GetProcessById(instance.Id).Kill()
        printfn "waiting for process to exit"
        let! waitAsync = Async.AwaitEvent(instance.Exited)
        printfn "process exited with code: %d" instance.ExitCode

    }
deadlockRepro |> Async.RunSynchronously

Fix:

let startProcessWaitNoHang () =
    async {
        use instance =
            new System.Diagnostics.Process(
                StartInfo =
                    System.Diagnostics.ProcessStartInfo
                        (
                            FileName = "notepad.exe",
                            WorkingDirectory = ".",
                            Arguments = "",
                            CreateNoWindow = false,
                            UseShellExecute = false,
                            RedirectStandardOutput = false,
                            WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
                            Verb =   "runas"
                        ),
                EnableRaisingEvents = true)

        let! waitAsync = Async.StartChild(Async.AwaitEvent(instance.Exited))

        if not (instance.Start()) then
            return raise <| System.InvalidOperationException("Could not start the process")
        else
            printfn "killing the process"
            System.Diagnostics.Process.GetProcessById(instance.Id).Kill()
            printfn "waiting for process to exit"
            let! _ = waitAsync
            printfn "process exited with code: %d" instance.ExitCode

    }

startProcessWaitNoHang() |> Async.RunSynchronously