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

context: canceling a context should clear the timeout in nodejs #72797

Open
dadanhrn opened this issue Mar 11, 2025 · 8 comments
Open

context: canceling a context should clear the timeout in nodejs #72797

dadanhrn opened this issue Mar 11, 2025 · 8 comments
Labels
arch-wasm WebAssembly issues BugReport Issues describing a possible bug in the Go implementation. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-JS

Comments

@dadanhrn
Copy link

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

$ go version
go version go1.23.6 linux/amd64

Does this issue reproduce with the latest release?

Yes (1.24.0)

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

go env Output
$ go env
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/dadanhrn/.cache/go-build'
GOENV='/home/dadanhrn/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/dadanhrn/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/dadanhrn/go'
GOPRIVATE=''
GOPROXY='direct'
GOROOT='/usr/lib/golang'
GOSUMDB='off'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/lib/golang/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.6'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/dadanhrn/.config/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/dadanhrn/Documents/Kuliah/trapeze-go_debug/go.mod'
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 -ffile-prefix-map=/tmp/go-build3041393783=/tmp/go-build -gno-record-gcc-switches'
GOROOT/bin/go version: go version go1.23.6 linux/amd64
GOROOT/bin/go tool compile -V: compile version go1.23.6
uname -sr: Linux 6.13.4-200.fc41.x86_64
/lib64/libc.so.6: GNU C Library (GNU libc) stable release version 2.40.
gdb --version: GNU gdb (Fedora Linux) 16.2-1.fc41

What did you do?

Example code: https://go.dev/play/p/j5I16KsARN_i
The HTTP request can be replaced with a query to an SQL database or just any operation that takes context.Context. Set the timeout long enough so the operation can finish before the context timeouts. Notice the defer cancel() statement.

I compiled the program with GOOS=js GOARCH=wasm go build -o main.wasm and run the wasm binary on Node.js v22.13.0 with the provided wasm_exec.js. I added several print statements around these lines to help with debugging https://pastebin.com/ZTUnDYwn

Here's the Node.js script to run the wasm binary https://pastebin.com/CDNMS5Au

What did you expect to see?

###### SCHEDULE TIMEOUT EVENT 8 2951
Get "https://www.google.com": dial tcp: lookup www.google.com on [::1]:53: write udp 127.0.0.1:8->[::1]:53: write: Connection reset by peer
###### CLEAR TIMEOUT EVENT 8

I suppose the connection reset error is expected due to this https://cs.opensource.google/go/go/+/master:src/net/http/roundtrip_js.go;l=49-57?q=jsFetchDisabled&ss=go%2Fgo

What did you see instead?

###### SCHEDULE TIMEOUT EVENT 8 2951
Get "https://www.google.com": dial tcp: lookup www.google.com on [::1]:53: write udp 127.0.0.1:8->[::1]:53: write: Connection reset by peer
###### TRIGGER TIMEOUT EVENT 8
/home/dadanhrn/Documents/Kuliah/debug_timer/wasm_exec.js:560
                                throw new Error("Go program has already exited");
                                ^

Error: Go program has already exited
    at globalThis.Go._resume (/home/dadanhrn/Documents/Kuliah/debug_timer/wasm_exec.js:560:11)
    at Timeout._onTimeout (/home/dadanhrn/Documents/Kuliah/debug_timer/wasm_exec.js:286:14)
    at listOnTimeout (node:internal/timers:594:17)
    at process.processTimers (node:internal/timers:529:7)

Node.js v22.13.0

I suppose it makes sense that calling the cancel() function (through defer in this case) should also clear the timeout in the host Node.js runtime.

@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Mar 11, 2025
@seankhliao seankhliao changed the title context: context with timeout on wasm crashes the host Node.js runtime context: cancelling a context should clear the timeout in nodejs Mar 14, 2025
@seankhliao seankhliao added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. arch-wasm WebAssembly issues OS-JS labels Mar 14, 2025
@dr2chase
Copy link
Contributor

@golang/wasm

@johanbrandhorst
Copy link
Member

Looks like maybe we're not cleaning up timers when the program exits? These are created by https://cs.opensource.google/go/go/+/master:src/runtime/lock_js.go;l=267;bpv=1;bpt=0?q=scheduleTimeoutEvent&ss=go%2Fgo.

@dadanhrn
Copy link
Author

Adding something like this in the scheduleTimeoutEvent implementation eliminates the crash, but it still leaves a dangling timeout which prevents the Node.js runtime from terminating immediately after the wasm call

"runtime.scheduleTimeoutEvent": (sp) => {
    sp >>>= 0;
    const id = this._nextCallbackTimeoutID;
    this._nextCallbackTimeoutID++;
    this._scheduledTimeouts.set(id, setTimeout(
        () => {
            if (this.exited) { // <== this
                return
            }
            this._resume();
            while (this._scheduledTimeouts.has(id)) {
                // for some reason Go failed to register the timeout event, log and try>
                // (temporary workaround for https://github.com/golang/go/issues/28975)
                console.warn("scheduleTimeoutEvent: missed timeout event");
                this._resume();
            }
        },
        getInt64(sp + 8),
    ));
    this.mem.setInt32(sp + 16, id, true);
},

@Zxilly
Copy link
Member

Zxilly commented Mar 18, 2025

maybe we can cancel all _scheduledTimeouts in the "runtime.wasmExit"?

@Zxilly
Copy link
Member

Zxilly commented Mar 18, 2025

diff --git a/lib/wasm/wasm_exec.js b/lib/wasm/wasm_exec.js
--- a/lib/wasm/wasm_exec.js	(revision 6fb7bdc96d0398fab313586fba6fdc89cc14c679)
+++ b/lib/wasm/wasm_exec.js	(date 1739646571863)
@@ -243,6 +243,10 @@
 						delete this._goRefCounts;
 						delete this._ids;
 						delete this._idPool;
+						for (const id of this._scheduledTimeouts) {
+							clearTimeout(id[1]);
+						}
+						delete this._scheduledTimeouts;
 						this.exit(code);
 					},
 

@dadanhrn
Copy link
Author

maybe we can cancel all _scheduledTimeouts in the "runtime.wasmExit"?

I confirm that this works. However,

  • are there any specific cases where the wasm program exits without calling runtime.wasmExit? (Other than when the host Node.js runtime gets interrupted)
  • I suppose this works as a garbage collection measure, which only suppresses the symptom rather than solving the root cause

@Zxilly
Copy link
Member

Zxilly commented Mar 26, 2025

  1. runtime.wasmExit was called from runtime.exit, I think this will always happen.

  2. I agree that it's just a workaround, but since wasm is single-threaded, we rely on js to do the swapping of contexts and scheduling. I think a meaningful fix should wait until WASM Thread is implemented.

@dmitshur dmitshur changed the title context: cancelling a context should clear the timeout in nodejs context: canceling a context should clear the timeout in nodejs Mar 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-wasm WebAssembly issues BugReport Issues describing a possible bug in the Go implementation. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-JS
Projects
None yet
Development

No branches or pull requests

6 participants