fix(workers): Make `worker.terminate()` not block the current thread (#13941)
Calling `worker.terminate()` used to kill the worker's isolate and
then block until the worker's thread finished. This blocks the calling
thread if the worker's event loop was blocked in a sync op (as with
`Deno.sleepSync`), which wasn't realized at the time, but since the
worker's isolate was killed at that moment, it would not block the
calling thread if the worker was in a JS endless loop.
However, in #12831, in order to work around a V8 bug, worker
termination was changed to first set a signal to let the worker event
loop know that termination has been requested, and only kill the
isolate if the event loop has not finished after 2 seconds. However,
this change kept the blocking, which meant that JS endless loops in
the worker now blocked the parent for 2 seconds.
As it turns out, after #12831 it is fine to signal termination and
even kill the worker's isolate without waiting for the thread to
finish, so this change does that. However, that might leave the async
ops that receive messages and control data from the worker pending
after `worker.terminate()`, which leads to odd results from the op
sanitizer. Therefore, we set up a `CancelHandler` to cancel those ops
when the worker is terminated.
2022-04-27 12:22:47 -04:00
|
|
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
|
|
|
|
|
|
// Test that the panic in https://github.com/denoland/deno/issues/11342 does not
|
|
|
|
// happen when calling worker.terminate() after fixing
|
|
|
|
// https://github.com/denoland/deno/issues/13705
|
|
|
|
|
|
|
|
function getCodeBlobUrl(code) {
|
|
|
|
const blob = new Blob([code], { type: "text/javascript" });
|
|
|
|
return URL.createObjectURL(blob);
|
|
|
|
}
|
|
|
|
|
|
|
|
const WORKER2 = getCodeBlobUrl(`
|
|
|
|
console.log("Worker 2");
|
|
|
|
self.postMessage(undefined);
|
|
|
|
|
2022-06-13 15:28:00 -04:00
|
|
|
// We sleep synchronously for slightly under 2 seconds in order to make sure
|
|
|
|
// that worker 1 has closed, and that this worker's thread finishes normally
|
|
|
|
// rather than being killed (which happens 2 seconds after calling terminate).
|
|
|
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1800);
|
fix(workers): Make `worker.terminate()` not block the current thread (#13941)
Calling `worker.terminate()` used to kill the worker's isolate and
then block until the worker's thread finished. This blocks the calling
thread if the worker's event loop was blocked in a sync op (as with
`Deno.sleepSync`), which wasn't realized at the time, but since the
worker's isolate was killed at that moment, it would not block the
calling thread if the worker was in a JS endless loop.
However, in #12831, in order to work around a V8 bug, worker
termination was changed to first set a signal to let the worker event
loop know that termination has been requested, and only kill the
isolate if the event loop has not finished after 2 seconds. However,
this change kept the blocking, which meant that JS endless loops in
the worker now blocked the parent for 2 seconds.
As it turns out, after #12831 it is fine to signal termination and
even kill the worker's isolate without waiting for the thread to
finish, so this change does that. However, that might leave the async
ops that receive messages and control data from the worker pending
after `worker.terminate()`, which leads to odd results from the op
sanitizer. Therefore, we set up a `CancelHandler` to cancel those ops
when the worker is terminated.
2022-04-27 12:22:47 -04:00
|
|
|
console.log("Finished sleeping in worker 2");
|
|
|
|
`);
|
|
|
|
|
|
|
|
const WORKER1 = getCodeBlobUrl(`
|
|
|
|
console.log("Worker 1");
|
2022-05-17 16:27:17 -04:00
|
|
|
const worker = new Worker(${JSON.stringify(WORKER2)}, { type: "module" });
|
fix(workers): Make `worker.terminate()` not block the current thread (#13941)
Calling `worker.terminate()` used to kill the worker's isolate and
then block until the worker's thread finished. This blocks the calling
thread if the worker's event loop was blocked in a sync op (as with
`Deno.sleepSync`), which wasn't realized at the time, but since the
worker's isolate was killed at that moment, it would not block the
calling thread if the worker was in a JS endless loop.
However, in #12831, in order to work around a V8 bug, worker
termination was changed to first set a signal to let the worker event
loop know that termination has been requested, and only kill the
isolate if the event loop has not finished after 2 seconds. However,
this change kept the blocking, which meant that JS endless loops in
the worker now blocked the parent for 2 seconds.
As it turns out, after #12831 it is fine to signal termination and
even kill the worker's isolate without waiting for the thread to
finish, so this change does that. However, that might leave the async
ops that receive messages and control data from the worker pending
after `worker.terminate()`, which leads to odd results from the op
sanitizer. Therefore, we set up a `CancelHandler` to cancel those ops
when the worker is terminated.
2022-04-27 12:22:47 -04:00
|
|
|
|
|
|
|
worker.addEventListener("message", () => {
|
|
|
|
console.log("Terminating");
|
|
|
|
worker.terminate();
|
|
|
|
self.close();
|
|
|
|
});
|
|
|
|
`);
|
|
|
|
|
2022-05-17 16:27:17 -04:00
|
|
|
new Worker(WORKER1, { type: "module" });
|
fix(workers): Make `worker.terminate()` not block the current thread (#13941)
Calling `worker.terminate()` used to kill the worker's isolate and
then block until the worker's thread finished. This blocks the calling
thread if the worker's event loop was blocked in a sync op (as with
`Deno.sleepSync`), which wasn't realized at the time, but since the
worker's isolate was killed at that moment, it would not block the
calling thread if the worker was in a JS endless loop.
However, in #12831, in order to work around a V8 bug, worker
termination was changed to first set a signal to let the worker event
loop know that termination has been requested, and only kill the
isolate if the event loop has not finished after 2 seconds. However,
this change kept the blocking, which meant that JS endless loops in
the worker now blocked the parent for 2 seconds.
As it turns out, after #12831 it is fine to signal termination and
even kill the worker's isolate without waiting for the thread to
finish, so this change does that. However, that might leave the async
ops that receive messages and control data from the worker pending
after `worker.terminate()`, which leads to odd results from the op
sanitizer. Therefore, we set up a `CancelHandler` to cancel those ops
when the worker is terminated.
2022-04-27 12:22:47 -04:00
|
|
|
|
|
|
|
// Don't kill the process before worker 2 is finished.
|
|
|
|
setTimeout(() => {}, 3000);
|