summaryrefslogtreecommitdiff
path: root/src/platform/posix/loop.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-15 20:38:14 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-15 20:38:14 +0300
commitc2832a63547e6328afde25bb3b8145267eadef47 (patch)
treebf03ace79f01df10ddaffd70bea74678ab4ca219 /src/platform/posix/loop.c
parent3d012d707d0242780cd386868ab511846dc9b0af (diff)
Make the loop public and let the display drive it
An application that has its own descriptors and timers needed a second loop next to yasl's, and could not see yasl's key-repeat timer, clipboard pipes or reconnect timer. Now there is one wait: the application owns a yasl_loop, and yasl_init can run the display on it. - Public yasl_loop_*: descriptors, timers and signals. Descriptor callbacks return void. Timer IDs are 64 bits and never reused; a zero delay runs in the next step. A nested step or run fails with EBUSY, a step off the loop's thread with EPERM, and a quit made before a run ends it at once. An interrupted wait keeps its deadline. Creating a loop fails when it cannot make its wake pipe. - The display drives the loop through prepare, wait and check hooks instead of wrapping its step. Wayland finishes its read before any callback runs, so loop callbacks may create surfaces and do roundtrips. Readiness (queued events, an owed REFRESH) is derived in the step, so the wait never blocks over pending work. A draw that cannot proceed for want of a buffer is no longer counted as work. - yasl_init takes struct yasl_options. With loop and on_event, each step delivers events to on_event and yasl_wait_event fails with EBUSY. EALREADY only for the same options, EBUSY for others. - yasl_surface_request_frame: a REFRESH once the window can show a new frame, for pacing animation. - DISPLAY_LOST reports whether the display reconnects. Pull waits still fail with ENOTCONN after a final loss. - Events pushed from another thread wake the loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src/platform/posix/loop.c')
-rw-r--r--src/platform/posix/loop.c646
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;
}