[Proxying] Send messages via in-memory mailbox queues - #18852
Conversation
|
Current dependencies on/for this PR:
This comment was auto-generated by Graphite. |
73023ce to
6ba6e6d
Compare
|
The asan failures (but not other flakes) on test_pthread_dlopen_many seem to be resolved by #18776, so I don't think they're worth investigating here. |
| // Wait if possible and otherwise spin. | ||
| if (_emscripten_thread_supports_atomics_wait() && | ||
| __builtin_wasm_memory_atomic_wait32( | ||
| (int*)&thread->mailbox_refcount, count, -1) == 0) { |
There was a problem hiding this comment.
Can we no use something slightly higher level here such as emscripten_futex_wait?
There was a problem hiding this comment.
Yes, I suppose so. And there's not a great reason not to do so, even though I liked programming so close to the (virtual) metal.
There was a problem hiding this comment.
I think it would be good to use the emscripten_futex abstraction if we can.
If nothing else I think its nice to limit the places we use __builtin_wasm_memory_atomic_wait32 withing the emscripten codebase.
| // can be sure cleanup has finished first. | ||
|
|
||
| // clang-format off | ||
| EM_ASM({setTimeout(() => Atomics.store(HEAP32, $0 >> 2, 1))}, flag); |
There was a problem hiding this comment.
Does this need to be EM_ASM or can you just use emscripten_set_timeout?
There was a problem hiding this comment.
It needs to be an EM_ASM because we need to avoid callUserCallback. This code will run after the thread runtime has exited.
There was a problem hiding this comment.
I see, maybe a comment then?
Threads were previously notified of new work via postMessage messages that carried pointers to the task queues to execute. There was no way to synchronously pump or inspect these pending messages however, and there is no central registry of all task queues for a thread, so this mechanism afforded no way to discover or cancel pending work when a thread dies. In preparation for implementing work cancellation, move the pending messages into userspace by giving each thread a "mailbox", which is an `em_task_queue` in the pthread struct. Instead of using `postMessage`, proxying queues now use the thread mailbox API to notify threads of new work. Internally, thread mailboxes still use postMessage to schedule work to be executed when a thread returns to its event loop. Since the only task queues involved in postMessages are now at known locations relative to the pthread struct, there is no longer any need to store pointers to them in the postMessage messages themselves. Removing these pointers works around tricky notification and lifetime management edge cases that would have caused problems such as dropped work or use-after-free bugs in future PRs. When a thread dies because it exits or is canceled, it "closes" its mailbox by decrementing a refcount and waiting to observe a refcount of 0. At this point, the thread mailbox API ensures that no new messages will be enqueued on the mailbox. Because the postMessage messages no longer contain task queue pointers, it is safe to destroy the mailbox immediately after it is closed. A user-visible behavior change this introduces is that proxied work is more frequently completed _before_ a thread's main function begins running, since it no longer gets ordered behind the `run` message in the JS postMessage queue. A few tests are updated accordingly.
2f288d3 to
bc9b5ad
Compare
…e#18852) Threads were previously notified of new work via postMessage messages that carried pointers to the task queues to execute. There was no way to synchronously pump or inspect these pending messages however, and there is no central registry of all task queues for a thread, so this mechanism afforded no way to discover or cancel pending work when a thread dies. In preparation for implementing work cancellation, move the pending messages into userspace by giving each thread a "mailbox", which is an `em_task_queue` in the pthread struct. Instead of using `postMessage`, proxying queues now use the thread mailbox API to notify threads of new work. Internally, thread mailboxes still use postMessage to schedule work to be executed when a thread returns to its event loop. Since the only task queues involved in postMessages are now at known locations relative to the pthread struct, there is no longer any need to store pointers to them in the postMessage messages themselves. Removing these pointers works around tricky notification and lifetime management edge cases that would have caused problems such as dropped work or use-after-free bugs in future PRs. When a thread dies because it exits or is canceled, it "closes" its mailbox by decrementing a refcount and waiting to observe a refcount of 0. At this point, the thread mailbox API ensures that no new messages will be enqueued on the mailbox. Because the postMessage messages no longer contain task queue pointers, it is safe to destroy the mailbox immediately after it is closed.
…e#18852) Threads were previously notified of new work via postMessage messages that carried pointers to the task queues to execute. There was no way to synchronously pump or inspect these pending messages however, and there is no central registry of all task queues for a thread, so this mechanism afforded no way to discover or cancel pending work when a thread dies. In preparation for implementing work cancellation, move the pending messages into userspace by giving each thread a "mailbox", which is an `em_task_queue` in the pthread struct. Instead of using `postMessage`, proxying queues now use the thread mailbox API to notify threads of new work. Internally, thread mailboxes still use postMessage to schedule work to be executed when a thread returns to its event loop. Since the only task queues involved in postMessages are now at known locations relative to the pthread struct, there is no longer any need to store pointers to them in the postMessage messages themselves. Removing these pointers works around tricky notification and lifetime management edge cases that would have caused problems such as dropped work or use-after-free bugs in future PRs. When a thread dies because it exits or is canceled, it "closes" its mailbox by decrementing a refcount and waiting to observe a refcount of 0. At this point, the thread mailbox API ensures that no new messages will be enqueued on the mailbox. Because the postMessage messages no longer contain task queue pointers, it is safe to destroy the mailbox immediately after it is closed.
This function has not existing since emscripten-core#18852.
This function has not existed since #18852.
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in emscripten-core#18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in emscripten-core#18852. A better solution is to wrap to whole function in callUserCallback, which takes case of checking if the runtime is alive before calling into native code. Fixes: emscripten-core#20067
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in emscripten-core#18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in emscripten-core#18852. A better solution is to wrap to whole function in callUserCallback, which takes case of checking if the runtime is alive before calling into native code. Fixes: emscripten-core#20067
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in emscripten-core#18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in emscripten-core#18852. A better solution is to wrap to whole function in callUserCallback, which takes case of checking if the runtime is alive before calling into native code. Fixes: emscripten-core#20067
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in emscripten-core#18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in emscripten-core#18852. A better solution is to wrap to whole function in callUserCallback, which takes case of checking if the runtime is alive before calling into native code. Fixes: emscripten-core#20067
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in emscripten-core#18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in emscripten-core#18852. A better solution is to wrap to whole function in callUserCallback, which takes case of checking if the runtime is alive before calling into native code. Fixes: emscripten-core#20067
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in #18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in #18852. A better solution is to wrap to whole function in callUserCallback, which takes care of checking if the runtime is alive before calling into native code. Fixes: #20067
…pten-core#25064) This function has not existed since emscripten-core#18852.
The `checkMailbox` callback can occur after the thread has terminated. In this case calling into native code can trigger the `makeAbortWrapper` wrapper that is put around each native function which then results in a "program has already aborted!" error being thrown. Once solution to this is to make sure that the function which are called do not have `makeAbortWrapper` applied to them. This was the technique I used in emscripten-core#18754, but the list of functions became stale when emscripten_proxy_execute_task_queue was removed in emscripten-core#18852. A better solution is to wrap to whole function in callUserCallback, which takes care of checking if the runtime is alive before calling into native code. Fixes: emscripten-core#20067

Threads were previously notified of new work via postMessage messages that
carried pointers to the task queues to execute. There was no way to synchronously
pump or inspect these pending messages however, and there is no central registry
of all task queues for a thread, so this mechanism afforded no way to discover
or cancel pending work when a thread dies.
In preparation for implementing work cancellation, move the pending messages
into userspace by giving each thread a "mailbox", which is an
em_task_queueinthe pthread struct. Instead of using
postMessage, proxying queues now use thethread mailbox API to notify threads of new work.
Internally, thread mailboxes still use postMessage to schedule work to be
executed when a thread returns to its event loop. Since the only task queues
involved in postMessages are now at known locations relative to the pthread
struct, there is no longer any need to store pointers to them in the postMessage
messages themselves. Removing these pointers works around tricky notification
and lifetime management edge cases that would have caused problems such as
dropped work or use-after-free bugs in future PRs.
When a thread dies because it exits or is canceled, it "closes" its mailbox by
decrementing a refcount and waiting to observe a refcount of 0. At this point,
the thread mailbox API ensures that no new messages will be enqueued on the
mailbox. Because the postMessage messages no longer contain task queue pointers,
it is safe to destroy the mailbox immediately after it is closed.
A user-visible behavior change this introduces is that proxied work is more
frequently completed before a thread's main function begins running, since it
no longer gets ordered behind the
runmessage in the JS postMessage queue. Afew tests are updated accordingly.