summaryrefslogtreecommitdiff
path: root/tests/common/test_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 /tests/common/test_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 'tests/common/test_loop.c')
-rw-r--r--tests/common/test_loop.c204
1 files changed, 170 insertions, 34 deletions
diff --git a/tests/common/test_loop.c b/tests/common/test_loop.c
index 5c49691..bdc33b7 100644
--- a/tests/common/test_loop.c
+++ b/tests/common/test_loop.c
@@ -4,7 +4,9 @@
#include <assert.h>
#include <errno.h>
+#include <poll.h>
#include <pthread.h>
+#include <stdint.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
@@ -29,7 +31,7 @@ static void on_alarm(int signo)
}
struct post_log {
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
char order[8];
int count;
};
@@ -57,7 +59,7 @@ static void post_b(void *ud)
static void test_post_order(void)
{
- struct yasl_private_loop *l = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
assert(l);
assert(!yasl_private_loop_post(NULL, post_b, NULL));
assert(!yasl_private_loop_post(l, NULL, NULL));
@@ -65,20 +67,20 @@ static void test_post_order(void)
struct post_log log = {.loop = l};
assert(yasl_private_loop_post(l, post_a, &log));
assert(yasl_private_loop_post(l, post_b, &log));
- assert(yasl_private_loop_step(l, 0) >= 1);
+ assert(yasl_loop_step(l, 0) >= 1);
/* A post made while posts run waits for a later step. */
assert(log.count == 2 && memcmp(log.order, "ab", 2) == 0);
- assert(yasl_private_loop_step(l, 0) >= 1);
+ assert(yasl_loop_step(l, 0) >= 1);
assert(log.count == 3 && log.order[2] == 'c');
/* Destroy drops posts that never ran. */
assert(yasl_private_loop_post(l, post_b, &log));
- yasl_private_loop_destroy(l);
+ yasl_loop_destroy(l);
assert(log.count == 3);
}
struct thread_ctx {
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
bool kick;
int signo;
_Atomic bool ran;
@@ -105,27 +107,27 @@ static void *wake_thread(void *ud)
static void test_cross_thread(void)
{
- struct yasl_private_loop *l = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
assert(l);
struct thread_ctx post = {.loop = l};
pthread_t t;
assert(pthread_create(&t, NULL, wake_thread, &post) == 0);
while (!post.ran)
- assert(yasl_private_loop_step(l, -1) >= 0);
+ assert(yasl_loop_step(l, -1) >= 0);
pthread_join(t, NULL);
/* A kick ends an indefinite wait without running anything. */
struct thread_ctx kick = {.loop = l, .kick = true};
assert(pthread_create(&t, NULL, wake_thread, &kick) == 0);
- assert(yasl_private_loop_step(l, -1) >= 1);
+ assert(yasl_loop_step(l, -1) >= 1);
pthread_join(t, NULL);
- yasl_private_loop_destroy(l);
+ yasl_loop_destroy(l);
}
struct signal_log {
- struct yasl_private_loop *loop;
+ struct yasl_loop *loop;
int seen[NSIG];
int total;
bool unwatch;
@@ -138,7 +140,7 @@ static void on_signal(void *ud, int signo)
log->seen[signo]++;
log->total++;
if (log->unwatch)
- yasl_private_loop_unwatch_signals(log->loop);
+ yasl_loop_unwatch_signals(log->loop);
}
static volatile sig_atomic_t previous_handler_hits;
@@ -149,10 +151,10 @@ static void previous_handler(int signo)
previous_handler_hits++;
}
-static void step_until(struct yasl_private_loop *l, const int *counter, int want)
+static void step_until(struct yasl_loop *l, const int *counter, int want)
{
while (*counter < want)
- assert(yasl_private_loop_step(l, -1) >= 0);
+ assert(yasl_loop_step(l, -1) >= 0);
}
static void test_signals(void)
@@ -161,30 +163,26 @@ static void test_signals(void)
sigemptyset(&prev.sa_mask);
assert(sigaction(SIGUSR2, &prev, NULL) == 0);
- struct yasl_private_loop *l = yasl_private_loop_create();
- struct yasl_private_loop *other = yasl_private_loop_create();
+ struct yasl_loop *l = yasl_loop_create();
+ struct yasl_loop *other = yasl_loop_create();
assert(l && other);
struct signal_log log = {.loop = l};
const int bad[] = {SIGKILL};
- errno = 0;
- assert(!yasl_private_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL);
+ assert(yasl_loop_watch_signals(l, bad, 1, on_signal, &log) == -EINVAL);
const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD};
- errno = 0;
- assert(!yasl_private_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL);
- errno = 0;
- assert(!yasl_private_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL);
- assert(yasl_private_loop_watch_signals(l, sigs, 3, on_signal, &log));
+ assert(yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) == -EINVAL);
+ assert(yasl_loop_watch_signals(l, sigs, 3, NULL, &log) == -EINVAL);
+ assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log) == 0);
/* One signal belongs to one loop at a time. */
- errno = 0;
- assert(!yasl_private_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY);
+ assert(yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) == -EBUSY);
/* Deliveries before the callback runs coalesce per signal. */
raise(SIGUSR1);
raise(SIGUSR1);
raise(SIGUSR2);
- assert(yasl_private_loop_step(l, 0) >= 1);
+ assert(yasl_loop_step(l, 0) >= 1);
assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1);
assert(previous_handler_hits == 0);
@@ -203,13 +201,16 @@ static void test_signals(void)
int status;
assert(waitpid(child, &status, 0) == child);
- /* Replacing the watch restores the signals it drops. */
+ /* A loop has one watch, so a second cannot silently drop the first one's
+ * signals. Unwatching restores the previous actions. */
const int only_usr1[] = {SIGUSR1};
- assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log));
+ assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == -EBUSY);
+ yasl_loop_unwatch_signals(l);
+ assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0);
raise(SIGUSR2);
assert(previous_handler_hits == 1);
- assert(yasl_private_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log));
- yasl_private_loop_unwatch_signals(other);
+ assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log) == 0);
+ yasl_loop_unwatch_signals(other);
assert(previous_handler_hits == 1);
raise(SIGUSR2);
assert(previous_handler_hits == 2);
@@ -221,21 +222,153 @@ static void test_signals(void)
struct sigaction now;
assert(sigaction(SIGUSR1, NULL, &now) == 0);
assert(now.sa_handler == SIG_DFL);
- yasl_private_loop_unwatch_signals(l);
+ yasl_loop_unwatch_signals(l);
/* Destroy unwatches. */
log.unwatch = false;
- assert(yasl_private_loop_watch_signals(l, only_usr1, 1, on_signal, &log));
- yasl_private_loop_destroy(l);
+ assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log) == 0);
+ assert(yasl_loop_destroy(l) == 0);
assert(sigaction(SIGUSR1, NULL, &now) == 0);
assert(now.sa_handler == SIG_DFL);
- yasl_private_loop_destroy(other);
+ yasl_loop_destroy(other);
struct sigaction dfl = {.sa_handler = SIG_DFL};
sigemptyset(&dfl.sa_mask);
sigaction(SIGUSR2, &dfl, NULL);
}
+struct fd_log {
+ int calls;
+ short revents;
+};
+
+static void on_readable(void *ud, int fd, short revents)
+{
+ struct fd_log *log = ud;
+ char b;
+ if (revents & POLLIN)
+ assert(read(fd, &b, 1) == 1);
+ log->calls++;
+ log->revents = revents;
+}
+
+static void test_fds(void)
+{
+ yasl_loop *l = yasl_loop_create();
+ assert(l);
+ int p[2];
+ assert(pipe(p) == 0);
+ struct fd_log log = {0};
+ assert(yasl_loop_add_fd(l, -1, POLLIN, on_readable, &log) == -EINVAL);
+ assert(yasl_loop_add_fd(l, p[0], POLLIN, NULL, &log) == -EINVAL);
+ int id = yasl_loop_add_fd(l, p[0], POLLIN, on_readable, &log);
+ assert(id > 0);
+ assert(yasl_loop_step(l, 0) == 0 && log.calls == 0);
+ assert(write(p[1], "x", 1) == 1);
+ assert(yasl_loop_step(l, -1) == 1 && log.calls == 1 && (log.revents & POLLIN));
+ /* No requested events: a ready descriptor stays quiet. */
+ assert(yasl_loop_set_fd_events(l, id, 0) == 0);
+ assert(write(p[1], "x", 1) == 1);
+ assert(yasl_loop_step(l, 0) == 0 && log.calls == 1);
+ assert(yasl_loop_set_fd_events(l, id, POLLIN) == 0);
+ assert(yasl_loop_step(l, 0) == 1 && log.calls == 2);
+ assert(yasl_loop_del_fd(l, id) == 0);
+ assert(yasl_loop_del_fd(l, id) == -ENOENT);
+ assert(yasl_loop_set_fd_events(l, id, POLLIN) == -ENOENT);
+ assert(write(p[1], "x", 1) == 1);
+ assert(yasl_loop_step(l, 0) == 0 && log.calls == 2);
+ close(p[0]);
+ close(p[1]);
+ assert(yasl_loop_destroy(l) == 0);
+}
+
+struct reentry {
+ yasl_loop *loop;
+ int step, run;
+ bool quit;
+};
+
+static uint64_t reenter(void *ud, uint64_t id, uint64_t interval_ns)
+{
+ (void)id; (void)interval_ns;
+ struct reentry *r = ud;
+ r->step = yasl_loop_step(r->loop, 0);
+ r->run = yasl_loop_run(r->loop);
+ if (r->quit)
+ yasl_loop_quit(r->loop);
+ return 0;
+}
+
+static void *step_elsewhere(void *ud)
+{
+ return (void *)(intptr_t)yasl_loop_step(ud, 0);
+}
+
+static void test_reentry_and_quit(void)
+{
+ yasl_loop *l = yasl_loop_create();
+ assert(l);
+ /* A callback cannot step its own loop; the nested run keeps the outer quit. */
+ struct reentry r = {.loop = l, .quit = true};
+ assert(yasl_loop_add_timer(l, 0, reenter, &r));
+ assert(yasl_loop_run(l) == 0);
+ assert(r.step == -EBUSY && r.run == -EBUSY);
+
+ /* A quit before the run ends it at once, and the run clears it. */
+ yasl_loop_quit(l);
+ assert(yasl_loop_run(l) == 0);
+ r = (struct reentry){.loop = l, .quit = true};
+ assert(yasl_loop_add_timer(l, YASL_PRIVATE_MS_TO_NS(5), reenter, &r));
+ assert(yasl_loop_run(l) == 0 && r.step == -EBUSY);
+
+ /* Only the creating thread may step. */
+ pthread_t t;
+ void *result;
+ assert(pthread_create(&t, NULL, step_elsewhere, l) == 0);
+ pthread_join(t, &result);
+ assert((intptr_t)result == -EPERM);
+ assert(yasl_loop_destroy(l) == 0);
+}
+
+static void on_interrupt(int signo)
+{
+ (void)signo;
+}
+
+struct interrupter {
+ pthread_t target;
+ int delay_ms;
+};
+
+static void *interrupt_thread(void *ud)
+{
+ struct interrupter *i = ud;
+ sleep_ms(i->delay_ms);
+ pthread_kill(i->target, SIGUSR2);
+ return NULL;
+}
+
+/* An interrupted wait resumes with the time left, not the whole timeout. */
+static void test_interrupted_wait(void)
+{
+ struct sigaction sa = {.sa_handler = on_interrupt};
+ sigemptyset(&sa.sa_mask);
+ struct sigaction old;
+ assert(sigaction(SIGUSR2, &sa, &old) == 0);
+ yasl_loop *l = yasl_loop_create();
+ assert(l);
+ struct interrupter i = {.target = pthread_self(), .delay_ms = 60};
+ pthread_t t;
+ uint64_t start = yasl_time_ns();
+ assert(pthread_create(&t, NULL, interrupt_thread, &i) == 0);
+ assert(yasl_loop_step(l, 100) == 0);
+ uint64_t elapsed_ms = (yasl_time_ns() - start) / 1000000u;
+ pthread_join(t, NULL);
+ assert(elapsed_ms >= 90 && elapsed_ms < 140);
+ assert(yasl_loop_destroy(l) == 0);
+ sigaction(SIGUSR2, &old, NULL);
+}
+
int main(void)
{
signal(SIGALRM, on_alarm);
@@ -243,6 +376,9 @@ int main(void)
test_post_order();
test_cross_thread();
test_signals();
+ test_fds();
+ test_reentry_and_quit();
+ test_interrupted_wait();
printf("Loop tests passed.\n");
return 0;
}