Skip to content

Commit

Permalink
fix #3552: calling stop() now clears go timeouts
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 18, 2023
1 parent 7a225ff commit f38cbe6
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
new Foo();
```
* Terminate the Go GC when esbuild's `stop()` API is called ([#3552](https://github.com/evanw/esbuild/issues/3552))
If you use esbuild with WebAssembly and pass the `worker: false` flag to `esbuild.initialize()`, then esbuild will run the WebAssembly module on the main thread. If you do this within a Deno test and that test calls `esbuild.stop()` to clean up esbuild's resources, Deno may complain that a `setTimeout()` call lasted past the end of the test. This happens when the Go is in the middle of a garbage collection pass and has scheduled additional ongoing garbage collection work. Normally calling `esbuild.stop()` will terminate the web worker that the WebAssembly module runs in, which will terminate the Go GC, but that doesn't happen if you disable the web worker with `worker: false`.
With this release, esbuild will now attempt to terminate the Go GC in this edge case by calling `clearTimeout()` on these pending timeouts.
## 0.19.9
* Add support for transforming new CSS gradient syntax for older browsers
Expand Down
12 changes: 10 additions & 2 deletions lib/deno/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import type * as types from "../shared/types"
import * as common from "../shared/common"
import * as ourselves from "./wasm"

interface Go {
_scheduledTimeouts: Map<number, ReturnType<typeof setTimeout>>
}

declare const ESBUILD_VERSION: string
declare let WEB_WORKER_SOURCE_CODE: string
declare let WEB_WORKER_FUNCTION: (postMessage: (data: Uint8Array) => void) => (event: { data: Uint8Array | ArrayBuffer | WebAssembly.Module }) => void
declare let WEB_WORKER_FUNCTION: (postMessage: (data: Uint8Array) => void) => (event: { data: Uint8Array | ArrayBuffer | WebAssembly.Module }) => Go

export let version = ESBUILD_VERSION

Expand Down Expand Up @@ -91,10 +95,14 @@ const startRunningService = async (wasmURL: string | URL, wasmModule: WebAssembl
} else {
// Run esbuild on the main thread
let onmessage = WEB_WORKER_FUNCTION((data: Uint8Array) => worker.onmessage!({ data }))
let go: Go | undefined
worker = {
onmessage: null,
postMessage: data => setTimeout(() => onmessage({ data })),
postMessage: data => setTimeout(() => go = onmessage({ data })),
terminate() {
if (go)
for (let timeout of go._scheduledTimeouts.values())
clearTimeout(timeout)
},
}
}
Expand Down
12 changes: 10 additions & 2 deletions lib/npm/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import type * as types from "../shared/types"
import * as common from "../shared/common"
import * as ourselves from "./browser"

interface Go {
_scheduledTimeouts: Map<number, ReturnType<typeof setTimeout>>
}

declare const ESBUILD_VERSION: string
declare let WEB_WORKER_SOURCE_CODE: string
declare let WEB_WORKER_FUNCTION: (postMessage: (data: Uint8Array) => void) => (event: { data: Uint8Array | ArrayBuffer | WebAssembly.Module }) => void
declare let WEB_WORKER_FUNCTION: (postMessage: (data: Uint8Array) => void) => (event: { data: Uint8Array | ArrayBuffer | WebAssembly.Module }) => Go

export let version = ESBUILD_VERSION

Expand Down Expand Up @@ -85,10 +89,14 @@ const startRunningService = async (wasmURL: string | URL, wasmModule: WebAssembl
} else {
// Run esbuild on the main thread
let onmessage = WEB_WORKER_FUNCTION((data: Uint8Array) => worker.onmessage!({ data }))
let go: Go | undefined
worker = {
onmessage: null,
postMessage: data => setTimeout(() => onmessage({ data })),
postMessage: data => setTimeout(() => go = onmessage({ data })),
terminate() {
if (go)
for (let timeout of go._scheduledTimeouts.values())
clearTimeout(timeout)
},
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/shared/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ onmessage = ({ data: wasm }: { data: WebAssembly.Module | string }) => {
stdin.push(data)
if (resumeStdin) resumeStdin()
}
return go
}

fs.read = (
Expand Down Expand Up @@ -76,6 +77,8 @@ onmessage = ({ data: wasm }: { data: WebAssembly.Module | string }) => {
postMessage(error)
},
)

return go
}

async function tryToInstantiateModule(wasm: WebAssembly.Module | string, go: Go): Promise<WebAssembly.Instance> {
Expand Down
4 changes: 0 additions & 4 deletions scripts/deno-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ function test(name, backends, fn) {
promise.then(cancel, cancel)
promise.then(resolve, reject)
}),

// It's ok that the Go WebAssembly runtime uses "setTimeout"
sanitizeResources: false,
sanitizeOps: false,
})

for (const backend of backends) {
Expand Down

0 comments on commit f38cbe6

Please sign in to comment.