Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasm: Handling EPIPE from WASM seems impossible? #59099

Open
evanw opened this issue Mar 17, 2023 · 4 comments
Open

wasm: Handling EPIPE from WASM seems impossible? #59099

evanw opened this issue Mar 17, 2023 · 4 comments
Labels
arch-wasm WebAssembly issues NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@evanw
Copy link

evanw commented Mar 17, 2023

What version of Go are you using (go version)?

$ go version
go version go1.20.2 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/evan/.cache/go-build"
GOENV="/home/evan/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/evan/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/evan/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/evan/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/evan/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.20.2"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build4211878008=/tmp/go-build -gno-record-gcc-switches"

What did you do?

I'm the author of esbuild, a JavaScript build tool written in Go. One way to run esbuild is to use node's WebAssembly implementation with the .wasm file that Go generates. The esbuild library launches a child process that runs the .wasm file and communicates with the parent process over stdin+stdout. When the parent process exits, the child process is supposed to detect that stdout is no longer writable and exit. However, I'm seeing something like this instead:

Click to expand Go's verbose fatal panic log message...
fatal error: too many writes on closed pipe

goroutine 6 [running]:
runtime.throw({0x9970e, 0x1e})
	runtime/panic.go:1047 +0x3 fp=0x838ed8 sp=0x838eb0 pc=0x12240003
os.sigpipe()
	runtime/os_js.go:144 +0x2 fp=0x838ef0 sp=0x838ed8 pc=0x13b60002
os.epipecheck(...)
	os/file_unix.go:205
os.(*File).Write(0x80c020, {0x86a8c0, 0x21, 0x40})
	os/file.go:183 +0x2d fp=0x838f78 sp=0x838ef0 pc=0x1604002d
main.runService.func1()
	github.com/evanw/esbuild/cmd/esbuild/service.go:99 +0x7 fp=0x838fe0 sp=0x838f78 pc=0x1eeb0007
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x838fe8 sp=0x838fe0 pc=0x14060001
created by main.runService
	github.com/evanw/esbuild/cmd/esbuild/service.go:97 +0x1e

goroutine 1 [chan receive]:
runtime.gopark(0xb05b0, 0x832178, 0xe, 0x17, 0x2)
	runtime/proc.go:381 +0x28 fp=0x895bc8 sp=0x895ba0 pc=0x124b0028
runtime.chanrecv(0x832120, 0x0, 0x1)
	runtime/chan.go:583 +0x7f fp=0x895c50 sp=0x895bc8 pc=0x106d007f
runtime.chanrecv1(0x832120, 0x0)
	runtime/chan.go:442 +0x2 fp=0x895c78 sp=0x895c50 pc=0x106b0002
github.com/evanw/esbuild/internal/helpers.(*ThreadSafeWaitGroup).Wait(...)
	github.com/evanw/esbuild/internal/helpers/waitgroup.go:36
main.runService.func2()
	github.com/evanw/esbuild/cmd/esbuild/service.go:114 +0x8 fp=0x895c98 sp=0x895c78 pc=0x1eea0008
main.runService(0x1)
	github.com/evanw/esbuild/cmd/esbuild/service.go:160 +0x5c fp=0x895de0 sp=0x895c98 pc=0x1ee8005c
main.main()
	github.com/evanw/esbuild/cmd/esbuild/main.go:236 +0x9e fp=0x895f88 sp=0x895de0 pc=0x1ee1009e
runtime.main()
	runtime/proc.go:250 +0x32 fp=0x895fe0 sp=0x895f88 pc=0x12450032
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x895fe8 sp=0x895fe0 pc=0x14060001

goroutine 2 [force gc (idle)]:
runtime.gopark(0xb0748, 0x3a4470, 0x11, 0x14, 0x1)
	runtime/proc.go:381 +0x28 fp=0x826fb8 sp=0x826f90 pc=0x124b0028
runtime.goparkunlock(...)
	runtime/proc.go:387
runtime.forcegchelper()
	runtime/proc.go:305 +0x1f fp=0x826fe0 sp=0x826fb8 pc=0x1248001f
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x826fe8 sp=0x826fe0 pc=0x14060001
created by runtime.init.5
	runtime/proc.go:293 +0x2

goroutine 3 [GC sweep wait]:
runtime.gopark(0xb0748, 0x3a4800, 0xc, 0x14, 0x1)
	runtime/proc.go:381 +0x28 fp=0x827798 sp=0x827770 pc=0x124b0028
runtime.goparkunlock(...)
	runtime/proc.go:387
runtime.bgsweep(0x82a000)
	runtime/mgcsweep.go:278 +0xf fp=0x8277d0 sp=0x827798 pc=0x1179000f
runtime.gcenable.func1()
	runtime/mgc.go:178 +0x2 fp=0x8277e0 sp=0x8277d0 pc=0x110d0002
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x8277e8 sp=0x8277e0 pc=0x14060001
created by runtime.gcenable
	runtime/mgc.go:178 +0x8

goroutine 4 [GC scavenge wait]:
runtime.gopark(0xb0748, 0x3a4a40, 0xd, 0x14, 0x2)
	runtime/proc.go:381 +0x28 fp=0x827f80 sp=0x827f58 pc=0x124b0028
runtime.goparkunlock(...)
	runtime/proc.go:387
runtime.(*scavengerState).park(0x3a4a40)
	runtime/mgcscavenge.go:400 +0xd fp=0x827fa8 sp=0x827f80 pc=0x1160000d
runtime.bgscavenge(0x82a000)
	runtime/mgcscavenge.go:628 +0x4 fp=0x827fd0 sp=0x827fa8 pc=0x11650004
runtime.gcenable.func2()
	runtime/mgc.go:179 +0x2 fp=0x827fe0 sp=0x827fd0 pc=0x110c0002
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x827fe8 sp=0x827fe0 pc=0x14060001
created by runtime.gcenable
	runtime/mgc.go:179 +0xe

goroutine 5 [finalizer wait]:
runtime.gopark(0xb05e8, 0x3c16f0, 0x10, 0x14, 0x1)
	runtime/proc.go:381 +0x28 fp=0x826738 sp=0x826710 pc=0x124b0028
runtime.runfinq()
	runtime/mfinal.go:193 +0x1f fp=0x8267e0 sp=0x826738 pc=0x1104001f
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x8267e8 sp=0x8267e0 pc=0x14060001
created by runtime.createfing
	runtime/mfinal.go:163 +0xd

goroutine 7 [waiting]:
runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x1)
	runtime/proc.go:381 +0x28 fp=0x828f90 sp=0x828f68 pc=0x124b0028
runtime.handleEvent()
	runtime/lock_js.go:257 +0x1b fp=0x828fe0 sp=0x828f90 pc=0x10a3001b
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x828fe8 sp=0x828fe0 pc=0x14060001
created by runtime.beforeIdle
	runtime/lock_js.go:207 +0x1a

goroutine 8 [chan receive]:
runtime.gopark(0xb05b0, 0x8331f8, 0xe, 0x17, 0x2)
	runtime/proc.go:381 +0x28 fp=0x839ea0 sp=0x839e78 pc=0x124b0028
runtime.chanrecv(0x8331a0, 0x839f88, 0x1)
	runtime/chan.go:583 +0x7f fp=0x839f28 sp=0x839ea0 pc=0x106d007f
runtime.chanrecv1(0x8331a0, 0x839f88)
	runtime/chan.go:442 +0x2 fp=0x839f50 sp=0x839f28 pc=0x106b0002
main.(*serviceType).sendRequest(0x80fdd0, {0x441a0, 0xaafe30})
	github.com/evanw/esbuild/cmd/esbuild/service.go:192 +0xb fp=0x839fa0 sp=0x839f50 pc=0x1eed000b
main.runService.func3()
	github.com/evanw/esbuild/cmd/esbuild/service.go:125 +0x3 fp=0x839fe0 sp=0x839fa0 pc=0x1ee90003
runtime.goexit()
	runtime/asm_wasm.s:399 +0x1 fp=0x839fe8 sp=0x839fe0 pc=0x14060001
created by main.runService
	github.com/evanw/esbuild/cmd/esbuild/service.go:122 +0x41

What did you expect to see?

I expect os.Stdout.Write to return EPIPE if writing to os.Stdout fails with EPIPE.

What did you see instead?

It seems like if os.Stdout fails with EPIPE, Go's standard library throws an error that as far as I can tell is impossible to catch. Things I tried:

  1. I tried adding a deferred call to recover(), but it's never called.

  2. I tried calling signal.Ignore(syscall.SIGPIPE) but there is no SIGPIPE signal when building for WASM.

I dug deeper and discovered that Go's implementation for SIGPIPE calls throw which then calls fatalthrow which I assume is impossible to catch. I'm assuming this is a bug with Go because I don't understand why it would be desirable for a Go program to unconditionally crash when encountering a normal error condition such as (in this case) the parent process exiting.

I just need some way to make Go not do this, but I don't care exactly how. From my perspective I don't want to deal with SIGPIPE at all because it's a nuisance so ideally os.Stdout.Write would just return EPIPE. But I'm also willing to call signal.Ignore(syscall.SIGPIPE) before os.Stdout.Write if that's the desired solution instead.

@bcmills
Copy link
Contributor

bcmills commented Mar 17, 2023

(attn @golang/wasm)

@bcmills bcmills added arch-wasm WebAssembly issues NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Mar 17, 2023
@bcmills bcmills added this to the Backlog milestone Mar 17, 2023
@ianlancetaylor
Copy link
Contributor

In discussion on #11845 we decided that the right approach was that if we get an EPIPE error when writing to os.Stdout or os.Stderr then we should raise a SIGPIPE signal and let the program handle it or exit. If we get an EPIPE error on some other descriptor then we simply return it to the caller. That gives us reasonable behavior for both command line programs to write to stdout/stderr and for networking programs. At least, it gives us reasonable behavior on Unix.

Now, it happens that because the build tag for os/file_unix.go includes js-wasm, that behavior applies to js-wasm as well (it's in the function epipecheck). But there it probably doesn't make any sense. I suspect that the correct fix here is for sigpipe in runtime/os_js.go to do nothing. That should cause the EPIPE error to be returned to the caller.

@jmwolfe
Copy link

jmwolfe commented Nov 13, 2023

This issue affects many users of TeamCity and Jenkins that use esbuild (that uses GO). Hopefully it does not languish in the backlog for too long.

@egasimus
Copy link

Still languishing? 😁

fatal error: too many writes on closed pipe

goroutine 1 [running]:
... cue 7 goroutines' worth of stack trace ...

From esbuild-wasm 0.9.11 on node 21.5.0 😭

egasimus added a commit to hackbg/ganesha that referenced this issue Jan 22, 2024
- evanw/esbuild#1662
- golang/go#59099

confusing output from esbuild-wasm even after supposed end of process
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-wasm WebAssembly issues NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

5 participants