Errata

Go is blocked trying to read from the channel (exec\exec.go):

for range c.goroutine { if err := <-c.errch; err != nil && copyError == nil 
{ copyError = err } }

I think that the problem happens if you specify an io.Writer as cmd.Stdout 
and such writer doesn’t satisfy os.File (Output() uses a bytes.Buffer 
internally) because in that case a Pipe is used (to copy data between the 
writer/process). It seems like the pipe is inherited by the child process 
and isn’t closed until the child finish (blocking the goroutine that reads 
from the pipe and writes to the writer even if the parent process is gone).

I got a solution but not sure if it's good enough:

package main import ( "bytes" "context" "fmt" "io" "os/exec" "time" ) type 
WriterWithReadFrom interface { io.Writer io.ReaderFrom } type 
ContextWrappedWriter struct{ w WriterWithReadFrom c context.Context } type 
ReadFromResult struct{ n int64 err error } func (cww *ContextWrappedWriter) 
Write(p []byte) (n int, err error){ return cww.Write(p) } func (cww 
*ContextWrappedWriter) ReadFrom(r io.Reader) (n int64, err error){ if c, ok 
:= r.(io.Closer); ok { ch := make(chan ReadFromResult, 1) go func() { n, 
err := cww.w.ReadFrom(r) ch <- ReadFromResult{n, err} }() closed := false 
for ;; { select { case res := <-ch: return res.n, res.err case 
<-cww.c.Done(): if !closed{ closed = true err := c.Close() if err != nil { 
return 0, fmt.Errorf("error closing reader: %v", err) } } 
time.Sleep(time.Second * 1) } } } else { return cww.w.ReadFrom(r) } } func 
main() { ctx, cancel := context.WithTimeout(context.Background(), 
time.Duration(30000)*time.Millisecond) defer cancel() var Stdout, Stderr 
bytes.Buffer c := exec.CommandContext(ctx, "cmd.exe", "/c", "start", "/wait", 
"notepad.exe") c.Stderr = &ContextWrappedWriter{&Stderr, ctx} c.Stdout = 
&ContextWrappedWriter{&Stdout, ctx} err := c.Run() fmt.Println("end", "err", 
err, "stdout", Stdout.String(), "stderr", Stderr.String()) }





On Friday, February 4, 2022 at 4:49:41 PM UTC-3 Pablo Caballero wrote:

> Hi, community!
>
> The problem is happening in a more complex program but I’ve written a 
> simple program to reproduce it.
>
> package main import ( "context" "fmt" "os/exec" "time" ) func main() { 
> ctx, cancel := context.WithTimeout(context.Background(), time.Duration(
> 30000)*time.Millisecond) defer cancel() c := exec.CommandContext(ctx, 
> "cmd.exe", "/c", "start", "/wait", "notepad.exe") _, err := c.Output() 
> fmt.Println("end", "err", err) }
>
> Steps to reproduce:
>
>    - Just run the program and observe the process tree (using Process 
>    Explorer or similar)
>
> Expected behavior: c.Output() call returns as soon as cmd.exe process is 
> killed on context timeout.
>
> Observed behavior: the program blocks “forever” on c.Output() call until I 
> kill the notepad.exe process manually.
>
> Debugging shows that Go is blocked on (os/exec_windows.go):
>
> s, e := syscall.WaitForSingleObject(syscall.Handle(handle), 
> syscall.INFINITE)
>
> waiting on the cmd.exe process handle (even after it was killed).
>
> I also tested on Mac (was curious about the behavior on non-windows OSes) 
> and on Mac c.Output() returns as soon as the parent process is killed 
> (children process keeps running but it doesn’t cause c.Output() call to 
> block)
>
> Thank you in advance!
>
> Best regards!
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/011d6a62-7c98-4d8c-815d-983f18b22165n%40googlegroups.com.

Reply via email to