-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Implement epoll APIs in the JS filesystem #27207
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
Open
guybedford
wants to merge
6
commits into
emscripten-core:main
Choose a base branch
from
guybedford:epoll
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c907059
Implement epoll for the JS filesystem
guybedford c6dbe71
pr feedback
guybedford 1d1b2cd
Deliver epoll callback on the registering thread under pthreads
guybedford 24f7652
Share epoll instance state across dup'd fds
guybedford 3955157
Signal readiness from emscripten_epoll_set_callback
guybedford 53f754a
rebaseline
guybedford File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| * Copyright 2026 The Emscripten Authors. All rights reserved. | ||
| * Emscripten is available under two separate licenses, the MIT license and the | ||
| * University of Illinois/NCSA Open Source License. Both these licenses can be | ||
| * found in the LICENSE file. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <sys/epoll.h> | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| // EXPERIMENTAL. This API is new and may change (signature or semantics) over the | ||
| // next few releases; it is not yet covered by Emscripten's stability guarantees. | ||
| // | ||
| // Register a persistent readiness callback on an existing epoll fd (built with | ||
| // epoll_create1/epoll_ctl): instead of blocking in epoll_wait, the runtime | ||
| // invokes `callback` on the event loop whenever the epoll set has ready events | ||
| // waiting to be collected. The callback receives only `userdata`; it does not | ||
| // receive the events. To collect them it calls epoll_wait(epfd, ..., 0) itself | ||
| // - a non-blocking, zero-timeout wait - from within the callback (or later). | ||
| // Unlike epoll_wait it never blocks the calling stack, so it works without | ||
| // ASYNCIFY/JSPI. The callback is delivered on the calling thread's event loop: | ||
| // with pthreads the epoll readiness is tracked on the thread that owns the | ||
| // filesystem (the syscalls are proxied there), but each delivery is dispatched | ||
| // back to the thread that registered the callback. | ||
| // | ||
| // The callback fires on the next event-loop tick while the set has ready events | ||
| // that have not yet been collected, and keeps firing while any remain - it only | ||
| // signals that events are pending, so a callback that does not drain them (via | ||
| // epoll_wait) leaves them pending and re-fires. Whether a given fd is re-reported | ||
| // follows its per-fd trigger mode (set via epoll_ctl) exactly as epoll_wait does, | ||
| // so one callback can mix modes: | ||
| // - Level-triggered (the default): the fd is reported on the next tick whenever | ||
| // it is ready, and keeps re-firing while it stays ready. The runtime - not | ||
| // the application - drives the loop, so an fd that is structurally always | ||
| // ready (notably EPOLLOUT on a writable socket) will spin the event loop. | ||
| // Use one of the modes below for such fds. | ||
| // - EPOLLET (edge-triggered): reported once per readiness edge and not again | ||
| // until a fresh edge; usually preferable in this model. | ||
| // - EPOLLONESHOT: reported once, then the registration is disabled until you | ||
| // re-arm it with epoll_ctl(EPOLL_CTL_MOD). | ||
| // | ||
| // While armed it keeps the runtime alive only as long as it can still fire - i.e. | ||
| // while the epoll has at least one open watched fd. Once every watched fd is | ||
| // closed the set is terminal (it can never become ready again) and the callback | ||
| // stops holding the runtime, so no explicit disposal is required in that case. | ||
| // To dispose while open fds remain, either pass a NULL `callback` to unregister, | ||
| // or close the epoll fd. There is at most one callback per epoll: calling again | ||
| // replaces it (it does not stack). Returns 0, or a positive errno (EBADF if | ||
| // `epfd` is not an epoll fd). | ||
| typedef void (*em_epoll_callback)(void *userdata); | ||
| int emscripten_epoll_set_callback(int epfd, em_epoll_callback callback, void *userdata); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /* | ||
| * Copyright 2026 The Emscripten Authors. All rights reserved. | ||
| * Emscripten is available under two separate licenses, the MIT license and the | ||
| * University of Illinois/NCSA Open Source License. Both these licenses can be | ||
| * found in the LICENSE file. | ||
| */ | ||
|
|
||
| // Backs emscripten_epoll_set_callback under PTHREADS: the epoll readiness lives | ||
| // on the thread that owns the filesystem (the epoll syscalls are proxied | ||
| // there), but the user callback must run on the thread that registered it. This | ||
| // mirrors _emscripten_run_callback_on_thread in html5/callback.c, but reports | ||
| // back to the registering thread when a delivery completes so it can pace the | ||
| // next one - the callback collects the ready events (via a proxied epoll_wait) | ||
| // itself, so the FS thread must wait for that before firing again, or it would | ||
| // spin re-signalling the same still-ready level fd. | ||
|
|
||
| #include <assert.h> | ||
| #include <pthread.h> | ||
| #include <stdint.h> | ||
| #include <stdlib.h> | ||
|
|
||
| #include <emscripten/eventloop.h> | ||
| #include <emscripten/proxying.h> | ||
|
|
||
| #include "emscripten_internal.h" | ||
|
|
||
| typedef void (*em_epoll_callback)(void* userdata); | ||
|
|
||
| typedef struct epoll_callback_args_t { | ||
| em_epoll_callback callback; | ||
| void* userdata; | ||
| int token; | ||
| } epoll_callback_args_t; | ||
|
|
||
| // Runs on the registering thread: signal the user callback that events are | ||
| // pending (it collects them itself via epoll_wait). | ||
| static void do_epoll_callback(void* arg) { | ||
| epoll_callback_args_t* args = (epoll_callback_args_t*)arg; | ||
| args->callback(args->userdata); | ||
| } | ||
|
|
||
| // Runs back on the FS-owning thread once the delivery above has finished (or | ||
| // was cancelled because the target thread went away): let the JS layer | ||
| // re-derive. | ||
| static void do_epoll_done(void* arg) { | ||
| epoll_callback_args_t* args = (epoll_callback_args_t*)arg; | ||
| _emscripten_epoll_delivery_done(args->token); | ||
| free(arg); | ||
| } | ||
|
|
||
| void _emscripten_epoll_run_callback_on_thread(pthread_t t, | ||
| em_epoll_callback callback, | ||
| void* userdata, | ||
| int token) { | ||
| em_proxying_queue* q = emscripten_proxy_get_system_queue(); | ||
| epoll_callback_args_t* args = malloc(sizeof(epoll_callback_args_t)); | ||
| args->callback = callback; | ||
| args->userdata = userdata; | ||
| args->token = token; | ||
|
|
||
| if (!emscripten_proxy_callback( | ||
| q, t, do_epoll_callback, do_epoll_done, do_epoll_done, args)) { | ||
| assert(false && "emscripten_proxy_callback failed"); | ||
| } | ||
| } | ||
|
|
||
| // Runs on the owning thread: adjust its (thread-local) runtime keepalive so the | ||
| // epoll callback holds the thread it was registered on, not the FS thread. | ||
| static void do_epoll_keepalive(void* arg) { | ||
| if ((intptr_t)arg > 0) { | ||
| emscripten_runtime_keepalive_push(); | ||
| } else { | ||
| emscripten_runtime_keepalive_pop(); | ||
| } | ||
| } | ||
|
|
||
| void _emscripten_epoll_keepalive_on_thread(pthread_t t, int delta) { | ||
| em_proxying_queue* q = emscripten_proxy_get_system_queue(); | ||
| if (!emscripten_proxy_async( | ||
| q, t, do_epoll_keepalive, (void*)(intptr_t)delta)) { | ||
| assert(false && "emscripten_proxy_async failed"); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we maybe re-use the existing
_emscripten_run_callback_on_threadthat we use elsewhere?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under proxy to pthread, we need the completion on the FS thread to be able to handle the epoll completions, while also still proxying the event to to the listening thread.