diff options
Diffstat (limited to 'src/platform/posix')
| -rw-r--r-- | src/platform/posix/loop.c | 646 |
1 files changed, 308 insertions, 338 deletions
diff --git a/src/platform/posix/loop.c b/src/platform/posix/loop.c index 49132f8..6624575 100644 --- a/src/platform/posix/loop.c +++ b/src/platform/posix/loop.c @@ -6,7 +6,9 @@ #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <poll.h> +#include <pthread.h> #include <signal.h> #include <stdatomic.h> #include <stdlib.h> @@ -16,33 +18,33 @@ #define YASL_PRIVATE_LOOP_INIT_CAP 32 -typedef int (*yasl_private_loop_fd_fn)(int fd, short revents, void *ud); +typedef void (*fd_fn)(void *ud, int fd, short revents); -struct yasl_private_fd_source { - yasl_private_loop_fd_fn fn; +struct fd_source { + fd_fn fn; void *ud; int id; int fd; short events; }; -struct yasl_private_timer_source { +struct timer_source { yasl_private_timer_cb fn; void *ud; uint64_t interval_ns; uint64_t deadline_ns; - yasl_private_timer_id id; + uint64_t id; bool deleted; }; -struct yasl_private_post { +struct post { void (*fn)(void *ud); void *ud; - struct yasl_private_post *next; + struct post *next; }; /* Signals this loop watches; the previous actions are restored on unwatch. */ -struct yasl_private_signal_watch { +struct signal_watch { void (*cb)(void *ud, int signo); void *ud; int count; @@ -50,28 +52,34 @@ struct yasl_private_signal_watch { struct sigaction old[NSIG]; }; -struct yasl_private_loop { - struct yasl_private_fd_source *fds; +struct yasl_loop { + struct fd_source *fds; struct pollfd *pollfds; int count; int capacity; int next_id; - bool running; + pthread_t owner; + /* Set by yasl_loop_quit; run clears it when it returns. */ + bool quit; + bool stepping; int wake_rfd; int wake_wfd; int wake_watch; - struct yasl_private_timer_source *timers; + struct timer_source *timers; int timer_count; int timer_capacity; - uint32_t next_timer_id; + uint64_t next_timer_id; int timer_firing_depth; yasl_private_mutex post_mutex; - struct yasl_private_post *post_head; - struct yasl_private_post *post_tail; + struct post *post_head; + struct post *post_tail; - struct yasl_private_signal_watch *signals; + struct signal_watch *signals; + + struct yasl_private_loop_driver driver; + bool has_driver; }; /* Handlers run on any thread, so they only touch lock-free atomics: they mark @@ -79,7 +87,7 @@ struct yasl_private_loop { * stored as fd + 1 so that the zero initializer means no pipe. */ static atomic_int signal_wake_fd[NSIG]; static atomic_bool signal_pending[NSIG]; -static struct yasl_private_loop *signal_owner[NSIG]; +static struct yasl_loop *signal_owner[NSIG]; uint64_t yasl_private_loop_now_ns(void) { @@ -88,25 +96,25 @@ uint64_t yasl_private_loop_now_ns(void) return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; } -static void run_posts(struct yasl_private_loop *l) +static void run_posts(struct yasl_loop *l) { yasl_private_mutex_lock(&l->post_mutex); - struct yasl_private_post *p = l->post_head; + struct post *p = l->post_head; l->post_head = l->post_tail = NULL; yasl_private_mutex_unlock(&l->post_mutex); while (p) { - struct yasl_private_post *next = p->next; + struct post *next = p->next; p->fn(p->ud); free(p); p = next; } } -static void run_signals(struct yasl_private_loop *l) +static void run_signals(struct yasl_loop *l) { /* A callback may unwatch, so re-check the watch before each delivery. */ for (int signo = 1; signo < NSIG; signo++) { - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; if (!w) return; if (signal_owner[signo] != l || !atomic_exchange(&signal_pending[signo], false)) continue; @@ -114,148 +122,143 @@ static void run_signals(struct yasl_private_loop *l) } } -static int loop_wake_cb(int fd, short revents, void *ud) +static void loop_wake_cb(void *ud, int fd, short revents) { (void)revents; char buf[128]; ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0 || (n < 0 && errno == EINTR)) {} - struct yasl_private_loop *l = ud; + struct yasl_loop *l = ud; run_posts(l); run_signals(l); - return 0; } -struct yasl_private_loop *yasl_private_loop_create(void) +static bool open_wake_pipe(int p[2]) { - struct yasl_private_loop *l = calloc(1, sizeof(*l)); - if (!l) return NULL; +#if defined(__linux__) && defined(O_CLOEXEC) + return pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0; +#else + if (pipe(p) != 0) return false; + for (int i = 0; i < 2; i++) { + int flags = fcntl(p[i], F_GETFL, 0); + if (flags < 0 || fcntl(p[i], F_SETFL, flags | O_NONBLOCK) < 0 || + fcntl(p[i], F_SETFD, FD_CLOEXEC) < 0) { + int err = errno; + close(p[0]); + close(p[1]); + errno = err; + return false; + } + } + return true; +#endif +} +yasl_loop *yasl_loop_create(void) +{ + struct yasl_loop *l = calloc(1, sizeof(*l)); + if (!l) return NULL; l->capacity = YASL_PRIVATE_LOOP_INIT_CAP; l->fds = malloc((size_t)l->capacity * sizeof(*l->fds)); l->pollfds = malloc((size_t)l->capacity * sizeof(*l->pollfds)); - if (!l->fds || !l->pollfds) { - free(l->fds); - free(l->pollfds); - free(l); - return NULL; + int p[2]; + int err = 0; + if (!l->fds || !l->pollfds) + err = ENOMEM; + else if ((err = yasl_private_mutex_init(&l->post_mutex)) == 0 && !open_wake_pipe(p)) { + err = errno; + yasl_private_mutex_destroy(&l->post_mutex); } - - if (yasl_private_mutex_init(&l->post_mutex) != 0) { + if (err) { free(l->fds); free(l->pollfds); free(l); + errno = err; return NULL; } - + l->owner = pthread_self(); l->next_id = 1; l->next_timer_id = 1; - l->wake_rfd = -1; - l->wake_wfd = -1; - - int p[2]; -#if defined(__linux__) && defined(O_CLOEXEC) - if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == 0) { - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#else - if (pipe(p) == 0) { - int f0 = fcntl(p[0], F_GETFL, 0); - int f1 = fcntl(p[1], F_GETFL, 0); - if (f0 >= 0) fcntl(p[0], F_SETFL, f0 | O_NONBLOCK); - if (f1 >= 0) fcntl(p[1], F_SETFL, f1 | O_NONBLOCK); - fcntl(p[0], F_SETFD, FD_CLOEXEC); - fcntl(p[1], F_SETFD, FD_CLOEXEC); - l->wake_rfd = p[0]; - l->wake_wfd = p[1]; - } -#endif - - if (l->wake_rfd >= 0) { - l->wake_watch = yasl_private_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); - } + l->wake_rfd = p[0]; + l->wake_wfd = p[1]; + /* The first watch fits the initial capacity, so it cannot fail. */ + l->wake_watch = yasl_loop_add_fd(l, l->wake_rfd, POLLIN, loop_wake_cb, l); return l; } -void yasl_private_loop_destroy(struct yasl_private_loop *l) +int yasl_loop_destroy(yasl_loop *l) { - if (!l) return; - yasl_private_loop_unwatch_signals(l); - for (struct yasl_private_post *p = l->post_head; p;) { - struct yasl_private_post *next = p->next; + if (!l) return 0; + if (l->stepping || l->has_driver) return -EBUSY; + yasl_loop_unwatch_signals(l); + for (struct post *p = l->post_head; p;) { + struct post *next = p->next; free(p); p = next; } yasl_private_mutex_destroy(&l->post_mutex); - if (l->wake_watch > 0) - yasl_private_loop_del_fd(l, l->wake_watch); - if (l->wake_rfd >= 0) - close(l->wake_rfd); - if (l->wake_wfd >= 0) - close(l->wake_wfd); + close(l->wake_rfd); + close(l->wake_wfd); free(l->fds); free(l->pollfds); free(l->timers); free(l); + return 0; } -int yasl_private_loop_add_fd(struct yasl_private_loop *l, int fd, short events, - int (*fn)(int fd, short revents, void *ud), void *ud) +int yasl_loop_add_fd(yasl_loop *l, int fd, short events, + void (*fn)(void *ud, int fd, short revents), void *ud) { - if (!l || fd < 0) - return -1; - + if (!l || fd < 0 || !fn) return -EINVAL; if (l->count >= l->capacity) { - int new_cap = l->capacity ? l->capacity * 2 : YASL_PRIVATE_LOOP_INIT_CAP; - struct yasl_private_fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); - if (!new_fds) return -1; + int new_cap = l->capacity * 2; + struct fd_source *new_fds = realloc(l->fds, (size_t)new_cap * sizeof(*new_fds)); + if (!new_fds) return -ENOMEM; l->fds = new_fds; - struct pollfd *new_pollfds = realloc(l->pollfds, (size_t)new_cap * sizeof(*new_pollfds)); - if (!new_pollfds) return -1; + if (!new_pollfds) return -ENOMEM; l->pollfds = new_pollfds; l->capacity = new_cap; } - int id = l->next_id++; if (l->next_id <= 0) l->next_id = 1; - - l->fds[l->count] = (struct yasl_private_fd_source){ - .fn = fn, .ud = ud, .id = id, .fd = fd, .events = events, - }; - l->pollfds[l->count] = (struct pollfd){ - .fd = fd, .events = events, .revents = 0, - }; + l->fds[l->count] = (struct fd_source){.fn = fn, .ud = ud, .id = id, .fd = fd, .events = events}; + l->pollfds[l->count] = (struct pollfd){.fd = fd, .events = events}; l->count++; return id; } -int yasl_private_loop_del_fd(struct yasl_private_loop *l, int id) +static int fd_index(const struct yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->count--; - l->fds[i] = l->fds[l->count]; - l->pollfds[i] = l->pollfds[l->count]; - return 0; - } - } + for (int i = 0; l && id > 0 && i < l->count; i++) + if (l->fds[i].id == id) + return i; return -1; } -int yasl_private_loop_set_fd_events(struct yasl_private_loop *l, int id, short events) +int yasl_loop_del_fd(yasl_loop *l, int id) { - if (!l || id <= 0) return -1; - for (int i = 0; i < l->count; i++) { - if (l->fds[i].id == id) { - l->fds[i].events = events; - l->pollfds[i].events = events; - return 0; - } - } - return -1; + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->count--; + l->fds[i] = l->fds[l->count]; + l->pollfds[i] = l->pollfds[l->count]; + return 0; +} + +int yasl_loop_set_fd_events(yasl_loop *l, int id, short events) +{ + int i = fd_index(l, id); + if (i < 0) return -ENOENT; + l->fds[i].events = events; + l->pollfds[i].events = events; + return 0; +} + +short yasl_private_loop_revents(const yasl_loop *l, int id) +{ + int i = fd_index(l, id); + return i < 0 ? 0 : l->pollfds[i].revents; } static void wake_write(int fd) @@ -270,18 +273,23 @@ static void wake_write(int fd) errno = saved; } -void yasl_private_loop_kick(struct yasl_private_loop *l) +void yasl_private_loop_kick(yasl_loop *l) { - if (!l || l->wake_wfd < 0) return; - wake_write(l->wake_wfd); + if (l) wake_write(l->wake_wfd); } -bool yasl_private_loop_post(struct yasl_private_loop *l, void (*fn)(void *ud), void *ud) +void yasl_private_loop_wake(yasl_loop *l) { - if (!l || !fn || l->wake_wfd < 0) return false; - struct yasl_private_post *p = malloc(sizeof(*p)); + if (l && !pthread_equal(l->owner, pthread_self())) + wake_write(l->wake_wfd); +} + +bool yasl_private_loop_post(yasl_loop *l, void (*fn)(void *ud), void *ud) +{ + if (!l || !fn) return false; + struct post *p = malloc(sizeof(*p)); if (!p) return false; - *p = (struct yasl_private_post){.fn = fn, .ud = ud}; + *p = (struct post){.fn = fn, .ud = ud}; yasl_private_mutex_lock(&l->post_mutex); if (l->post_tail) l->post_tail->next = p; @@ -305,85 +313,49 @@ static bool signal_valid(int signo) return signo > 0 && signo < NSIG && signo != SIGKILL && signo != SIGSTOP; } -bool yasl_private_loop_watch_signals(struct yasl_private_loop *l, const int *signals, int n, - void (*cb)(void *ud, int signo), void *ud) +int yasl_loop_watch_signals(yasl_loop *l, const int *signals, int n, + void (*cb)(void *ud, int signo), void *ud) { - if (!l || !signals || n <= 0 || !cb || l->wake_wfd < 0) { - errno = EINVAL; - return false; - } + if (!l || !signals || n <= 0 || !cb) return -EINVAL; + if (l->signals) return -EBUSY; bool wanted[NSIG] = {0}; for (int i = 0; i < n; i++) { - if (!signal_valid(signals[i])) { - errno = EINVAL; - return false; - } - if (signal_owner[signals[i]] && signal_owner[signals[i]] != l) { - errno = EBUSY; - return false; - } + if (!signal_valid(signals[i])) return -EINVAL; + if (signal_owner[signals[i]]) return -EBUSY; wanted[signals[i]] = true; } - struct yasl_private_signal_watch *w = calloc(1, sizeof(*w)); - if (!w) return false; + struct signal_watch *w = calloc(1, sizeof(*w)); + if (!w) return -ENOMEM; w->cb = cb; w->ud = ud; - - /* Keep the old watch's saved actions for signals that stay watched, so - * that unwatch restores what the application had before any watch. */ - struct yasl_private_signal_watch *old = l->signals; struct sigaction sa = {.sa_handler = signal_handler, .sa_flags = SA_RESTART}; sigemptyset(&sa.sa_mask); for (int signo = 1; signo < NSIG; signo++) { if (!wanted[signo]) continue; - int prev = -1; - for (int i = 0; old && i < old->count; i++) - if (old->signals[i] == signo) prev = i; - if (prev >= 0) { - w->old[w->count] = old->old[prev]; - } else { - atomic_store(&signal_pending[signo], false); - atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); - if (sigaction(signo, &sa, &w->old[w->count]) != 0) { - int err = errno; - atomic_store(&signal_wake_fd[signo], 0); - for (int i = 0; i < w->count; i++) { - int done = w->signals[i]; - bool kept = false; - for (int j = 0; old && j < old->count; j++) - if (old->signals[j] == done) kept = true; - if (kept) continue; - sigaction(done, &w->old[i], NULL); - atomic_store(&signal_wake_fd[done], 0); - } - free(w); - errno = err; - return false; + atomic_store(&signal_pending[signo], false); + atomic_store(&signal_wake_fd[signo], l->wake_wfd + 1); + if (sigaction(signo, &sa, &w->old[w->count]) != 0) { + int err = errno; + atomic_store(&signal_wake_fd[signo], 0); + for (int i = 0; i < w->count; i++) { + sigaction(w->signals[i], &w->old[i], NULL); + atomic_store(&signal_wake_fd[w->signals[i]], 0); } + free(w); + return -err; } w->signals[w->count++] = signo; } - - /* Restore signals the old watch had that the new one drops. */ - for (int i = 0; old && i < old->count; i++) { - int signo = old->signals[i]; - if (wanted[signo]) continue; - sigaction(signo, &old->old[i], NULL); - atomic_store(&signal_wake_fd[signo], 0); - atomic_store(&signal_pending[signo], false); - signal_owner[signo] = NULL; - } for (int i = 0; i < w->count; i++) signal_owner[w->signals[i]] = l; - free(old); l->signals = w; - return true; + return 0; } -void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) +void yasl_loop_unwatch_signals(yasl_loop *l) { if (!l || !l->signals) return; - struct yasl_private_signal_watch *w = l->signals; + struct signal_watch *w = l->signals; l->signals = NULL; for (int i = 0; i < w->count; i++) { int signo = w->signals[i]; @@ -395,19 +367,19 @@ void yasl_private_loop_unwatch_signals(struct yasl_private_loop *l) free(w); } -static void timer_cleanup_deleted(struct yasl_private_loop *l) +static void timer_cleanup_deleted(struct yasl_loop *l) { - if (!l || l->timer_firing_depth > 0) return; + if (l->timer_firing_depth > 0) return; int dest = 0; for (int src = 0; src < l->timer_count; src++) { - struct yasl_private_timer_source t = l->timers[src]; + struct timer_source t = l->timers[src]; l->timers[dest] = t; dest += !t.deleted; } l->timer_count = dest; } -bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_private_loop_timer_armed(const yasl_loop *l, yasl_private_timer_id id) { if (!l || id == 0) return false; for (int i = 0; i < l->timer_count; i++) @@ -416,7 +388,7 @@ bool yasl_private_loop_timer_armed(const struct yasl_private_loop *l, yasl_priva return false; } -int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct pollfd **fds) +int yasl_private_loop_poll_set(const yasl_loop *l, const struct pollfd **fds) { if (!l) { *fds = NULL; @@ -426,215 +398,213 @@ int yasl_private_loop_poll_set(const struct yasl_private_loop *l, const struct p return l->count; } -int yasl_private_loop_next_timeout_ms(const struct yasl_private_loop *l) +int yasl_private_loop_next_timeout_ms(const yasl_loop *l) { - if (!l || l->timer_count <= 0) return -1; + if (!l) return -1; uint64_t now = yasl_private_loop_now_ns(); uint64_t min_deadline = UINT64_MAX; - bool has_active = false; - for (int i = 0; i < l->timer_count; i++) { - if (!l->timers[i].deleted) { - has_active = true; - if (l->timers[i].deadline_ns < min_deadline) - min_deadline = l->timers[i].deadline_ns; - } - } - if (!has_active) return -1; + for (int i = 0; i < l->timer_count; i++) + if (!l->timers[i].deleted && l->timers[i].deadline_ns < min_deadline) + min_deadline = l->timers[i].deadline_ns; + if (min_deadline == UINT64_MAX) return -1; if (min_deadline <= now) return 0; - uint64_t diff = min_deadline - now; - uint64_t ms = (diff + 999999ull) / 1000000ull; - return (ms > (uint64_t)INT_MAX) ? INT_MAX : (int)ms; + uint64_t ms = (min_deadline - now + 999999ull) / 1000000ull; + return ms > (uint64_t)INT_MAX ? INT_MAX : (int)ms; } -yasl_private_timer_id yasl_private_loop_add_timer(struct yasl_private_loop *l, uint64_t interval_ns, - yasl_private_timer_cb cb, void *ud) +uint64_t yasl_loop_add_timer(yasl_loop *l, uint64_t interval_ns, + yasl_private_timer_cb cb, void *ud) { - if (!l || !cb || interval_ns == 0) return 0; - + if (!l || !cb) { + errno = EINVAL; + return 0; + } if (l->timer_count >= l->timer_capacity) { int new_cap = l->timer_capacity ? l->timer_capacity * 2 : 16; - struct yasl_private_timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); - if (!new_timers) return 0; + struct timer_source *new_timers = realloc(l->timers, (size_t)new_cap * sizeof(*new_timers)); + if (!new_timers) { + errno = ENOMEM; + return 0; + } l->timers = new_timers; l->timer_capacity = new_cap; } - - uint32_t id = l->next_timer_id++; - if (l->next_timer_id == 0) l->next_timer_id = 1; - - uint64_t now = yasl_private_loop_now_ns(); - l->timers[l->timer_count++] = (struct yasl_private_timer_source){ + /* 64 bits never wrap in practice, so an ID never names a newer timer. */ + uint64_t id = l->next_timer_id++; + l->timers[l->timer_count++] = (struct timer_source){ .fn = cb, .ud = ud, .id = id, .interval_ns = interval_ns, - .deadline_ns = now + interval_ns, - .deleted = false, + .deadline_ns = yasl_private_loop_now_ns() + interval_ns, }; - - yasl_private_loop_kick(l); + /* Owner-thread only, and the next wait computes its timeout from the + * timers, so no wake is needed. */ return id; } -bool yasl_private_loop_del_timer(struct yasl_private_loop *l, yasl_private_timer_id id) +bool yasl_loop_del_timer(yasl_loop *l, uint64_t id) { if (!l || id == 0) return false; for (int i = l->timer_count - 1; i >= 0; i--) { if (l->timers[i].id == id && !l->timers[i].deleted) { l->timers[i].deleted = true; - if (l->timer_firing_depth == 0) { - if (i == l->timer_count - 1) { - while (l->timer_count > 0 && l->timers[l->timer_count - 1].deleted) - l->timer_count--; - } else { - timer_cleanup_deleted(l); - } - } + timer_cleanup_deleted(l); return true; } } return false; } -int yasl_private_loop_step(struct yasl_private_loop *l, int timeout_ms) +bool yasl_private_loop_set_driver(yasl_loop *l, const struct yasl_private_loop_driver *driver) { - if (!l) return -1; - - int timer_to = yasl_private_loop_next_timeout_ms(l); - int effective_timeout = timeout_ms; - if (timer_to >= 0) { - if (effective_timeout < 0 || timer_to < effective_timeout) - effective_timeout = timer_to; - } - - if (l->count <= 0) { - if (effective_timeout > 0) { - struct timespec req = { - .tv_sec = effective_timeout / 1000, - .tv_nsec = (long)(effective_timeout % 1000) * 1000000L, - }; - nanosleep(&req, NULL); - } - } else { - int n; - do { - n = poll(l->pollfds, (nfds_t)l->count, effective_timeout); - } while (n < 0 && errno == EINTR); - - if (n < 0) return -1; + if (!l) return false; + if (driver && l->has_driver) { + errno = EBUSY; + return false; } + l->driver = driver ? *driver : (struct yasl_private_loop_driver){0}; + l->has_driver = driver != NULL; + return true; +} - int handled = 0; +bool yasl_private_loop_owned(const yasl_loop *l) +{ + return l && pthread_equal(l->owner, pthread_self()); +} - if (l->count > 0) { - int count = l->count; - struct yasl_private_fd_source sstack[32]; - struct pollfd pstack[32]; - struct yasl_private_fd_source *scopy = (count <= 32) ? sstack : malloc((size_t)count * sizeof(*scopy)); - struct pollfd *pcopy = (count <= 32) ? pstack : malloc((size_t)count * sizeof(*pcopy)); - if (!scopy || !pcopy) { - if (scopy && scopy != sstack) free(scopy); - if (pcopy && pcopy != pstack) free(pcopy); - return -1; - } +bool yasl_private_loop_stepping(const yasl_loop *l) +{ + return l && l->stepping; +} - memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); - memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); - - for (int i = 0; i < count; i++) { - if (pcopy[i].revents) { - short revents = pcopy[i].revents; - // Fast-path: check if source is still at index i before linear scan - bool valid = false; - if (i < l->count && l->fds[i].id == scopy[i].id) { - l->pollfds[i].revents = 0; - valid = true; - } else { - for (int j = 0; j < l->count; j++) { - if (l->fds[j].id == scopy[i].id) { - l->pollfds[j].revents = 0; - valid = true; - break; - } - } - } - if (valid && scopy[i].fn) { - scopy[i].fn(scopy[i].fd, revents, scopy[i].ud); - handled++; - } - } +/* poll with an EINTR retry that keeps the deadline. */ +static int poll_all(struct yasl_loop *l, int timeout_ms) +{ + uint64_t deadline = timeout_ms > 0 ? yasl_private_loop_now_ns() + YASL_PRIVATE_MS_TO_NS(timeout_ms) : 0; + for (;;) { + if (poll(l->pollfds, (nfds_t)l->count, timeout_ms) >= 0) return 0; + if (errno != EINTR) return -errno; + if (timeout_ms > 0) { + uint64_t now = yasl_private_loop_now_ns(); + timeout_ms = now >= deadline ? 0 : (int)((deadline - now + 999999ull) / 1000000ull); } + } +} +static int dispatch_fds(struct yasl_loop *l) +{ + int handled = 0; + int count = l->count; + struct fd_source sstack[32]; + struct pollfd pstack[32]; + struct fd_source *scopy = count <= 32 ? sstack : malloc((size_t)count * sizeof(*scopy)); + struct pollfd *pcopy = count <= 32 ? pstack : malloc((size_t)count * sizeof(*pcopy)); + if (!scopy || !pcopy) { if (scopy != sstack) free(scopy); if (pcopy != pstack) free(pcopy); + return -ENOMEM; + } + memcpy(scopy, l->fds, (size_t)count * sizeof(*scopy)); + memcpy(pcopy, l->pollfds, (size_t)count * sizeof(*pcopy)); + for (int i = 0; i < count; i++) { + if (!pcopy[i].revents) continue; + /* A callback may have removed or moved this watch. */ + int j = i < l->count && l->fds[i].id == scopy[i].id ? i : fd_index(l, scopy[i].id); + if (j < 0) continue; + l->pollfds[j].revents = 0; + scopy[i].fn(scopy[i].ud, scopy[i].fd, pcopy[i].revents); + handled++; } + if (scopy != sstack) free(scopy); + if (pcopy != pstack) free(pcopy); + return handled; +} - if (l->timer_count > 0) { - uint64_t now = yasl_private_loop_now_ns(); - l->timer_firing_depth++; - int initial_count = l->timer_count; - for (int i = 0; i < initial_count; i++) { - if (i >= l->timer_count) break; - if (l->timers[i].deleted || l->timers[i].deadline_ns > now) - continue; - - yasl_private_timer_id tid = l->timers[i].id; - uint64_t interval = l->timers[i].interval_ns; - void *ud = l->timers[i].ud; - struct yasl_private_timer_source timer_src = l->timers[i]; - - uint64_t next_interval = timer_src.fn(ud, tid, interval); - handled++; - - // Fast-path: timer is usually still at index i unless an earlier timer was deleted - if (i < l->timer_count && l->timers[i].id == tid) { - if (next_interval == 0 || l->timers[i].deleted) { - l->timers[i].deleted = true; - } else { - l->timers[i].interval_ns = next_interval; - if (l->timers[i].deadline_ns + next_interval > now) - l->timers[i].deadline_ns += next_interval; - else - l->timers[i].deadline_ns = now + next_interval; - } - } else { - for (int j = 0; j < l->timer_count; j++) { - if (l->timers[j].id == tid) { - if (next_interval == 0 || l->timers[j].deleted) { - l->timers[j].deleted = true; - } else { - l->timers[j].interval_ns = next_interval; - if (l->timers[j].deadline_ns + next_interval > now) - l->timers[j].deadline_ns += next_interval; - else - l->timers[j].deadline_ns = now + next_interval; - } - break; - } - } - } - } - l->timer_firing_depth--; - if (l->timer_firing_depth == 0) { - timer_cleanup_deleted(l); - } +static void reschedule(struct timer_source *t, uint64_t next, uint64_t now) +{ + if (next == 0 || t->deleted) { + t->deleted = true; + return; } + t->interval_ns = next; + t->deadline_ns = t->deadline_ns + next > now ? t->deadline_ns + next : now + next; +} +static int fire_timers(struct yasl_loop *l) +{ + int handled = 0; + uint64_t now = yasl_private_loop_now_ns(); + l->timer_firing_depth++; + /* Timers added by a callback wait for the next step. */ + int initial_count = l->timer_count; + for (int i = 0; i < initial_count && i < l->timer_count; i++) { + if (l->timers[i].deleted || l->timers[i].deadline_ns > now) + continue; + struct timer_source t = l->timers[i]; + uint64_t next = t.fn(t.ud, t.id, t.interval_ns); + handled++; + /* Compaction waits for depth zero, so index i still holds this timer. */ + reschedule(&l->timers[i], next, now); + } + l->timer_firing_depth--; + timer_cleanup_deleted(l); return handled; } -int yasl_private_loop_run(struct yasl_private_loop *l) +static int step(struct yasl_loop *l, int timeout_ms) { - if (!l) return -1; - l->running = true; - while (l->running) { - if (yasl_private_loop_step(l, -1) < 0) - return -1; + /* A driver cannot change during a step: its display cannot close then. */ + const struct yasl_private_loop_driver *d = l->has_driver ? &l->driver : NULL; + if (d && d->prepare(d->ud)) + timeout_ms = 0; + int timer_to = yasl_private_loop_next_timeout_ms(l); + if (timer_to >= 0 && (timeout_ms < 0 || timer_to < timeout_ms)) + timeout_ms = timer_to; + int r; + if (d && d->wait) { + r = d->wait(d->ud, timeout_ms); + /* The platform wait does not fill revents; read them now. */ + if (r >= 0) r = poll_all(l, 0); + } else { + r = poll_all(l, timeout_ms); } - return 0; + /* check closes what prepare opened, whatever the wait returned. */ + if (d) d->check(d->ud); + if (r < 0) return r; + if (d && d->deliver) d->deliver(d->ud); + int fds = dispatch_fds(l); + if (fds < 0) return fds; + return fds + fire_timers(l); +} + +int yasl_loop_step(yasl_loop *l, int timeout_ms) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + if (l->stepping) return -EBUSY; + l->stepping = true; + int r = step(l, timeout_ms); + l->stepping = false; + return r; +} + +int yasl_loop_run(yasl_loop *l) +{ + if (!l) return -EINVAL; + if (!pthread_equal(l->owner, pthread_self())) return -EPERM; + /* A nested run must not consume the outer run's quit. */ + if (l->stepping) return -EBUSY; + int r = 0; + while (!l->quit) { + r = yasl_loop_step(l, -1); + if (r < 0) break; + } + l->quit = false; + return r < 0 ? r : 0; } -void yasl_private_loop_quit(struct yasl_private_loop *l) +void yasl_loop_quit(yasl_loop *l) { - if (l) l->running = false; + if (l) l->quit = true; } |
