#define _GNU_SOURCE #define _DARWIN_C_SOURCE #include "yasl/loop.h" #include #include #include #include #include #include #include #include #include static void sleep_ms(int ms) { struct timespec ts = {.tv_sec = ms / 1000, .tv_nsec = (long)(ms % 1000) * 1000000L}; nanosleep(&ts, NULL); } /* Fail instead of hanging when a wake never arrives. */ static void on_alarm(int signo) { (void)signo; static const char msg[] = "loop test timed out\n"; ssize_t ignored = write(STDERR_FILENO, msg, sizeof(msg) - 1); (void)ignored; _exit(1); } struct post_log { struct yasl_loop *loop; char order[8]; int count; }; static void post_a(void *ud); static void post_c(void *ud) { struct post_log *log = ud; log->order[log->count++] = 'c'; } static void post_a(void *ud) { struct post_log *log = ud; log->order[log->count++] = 'a'; assert(yasl_loop_post(log->loop, post_c, log)); } static void post_b(void *ud) { struct post_log *log = ud; log->order[log->count++] = 'b'; } static void test_post_order(void) { struct yasl_loop *l = yasl_loop_create(); assert(l); assert(!yasl_loop_post(NULL, post_b, NULL)); assert(!yasl_loop_post(l, NULL, NULL)); struct post_log log = {.loop = l}; assert(yasl_loop_post(l, post_a, &log)); assert(yasl_loop_post(l, post_b, &log)); 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_loop_step(l, 0) >= 1); assert(log.count == 3 && log.order[2] == 'c'); /* Destroy drops posts that never ran. */ assert(yasl_loop_post(l, post_b, &log)); yasl_loop_destroy(l); assert(log.count == 3); } struct thread_ctx { struct yasl_loop *loop; bool kick; int signo; _Atomic bool ran; }; static void mark_ran(void *ud) { struct thread_ctx *ctx = ud; ctx->ran = true; } static void *wake_thread(void *ud) { struct thread_ctx *ctx = ud; sleep_ms(30); if (ctx->signo) kill(getpid(), ctx->signo); else if (ctx->kick) yasl_loop_kick(ctx->loop); else assert(yasl_loop_post(ctx->loop, mark_ran, ctx)); return NULL; } static void test_cross_thread(void) { 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_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_loop_step(l, -1) >= 1); pthread_join(t, NULL); yasl_loop_destroy(l); } struct signal_log { struct yasl_loop *loop; int seen[NSIG]; int total; bool unwatch; }; static void on_signal(void *ud, int signo) { struct signal_log *log = ud; assert(signo > 0 && signo < NSIG); log->seen[signo]++; log->total++; if (log->unwatch) yasl_loop_unwatch_signals(log->loop); } static volatile sig_atomic_t previous_handler_hits; static void previous_handler(int signo) { (void)signo; previous_handler_hits++; } static void step_until(struct yasl_loop *l, const int *counter, int want) { while (*counter < want) assert(yasl_loop_step(l, -1) >= 0); } static void test_signals(void) { struct sigaction prev = {.sa_handler = previous_handler}; sigemptyset(&prev.sa_mask); assert(sigaction(SIGUSR2, &prev, NULL) == 0); 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_loop_watch_signals(l, bad, 1, on_signal, &log) && errno == EINVAL); const int sigs[] = {SIGUSR1, SIGUSR2, SIGCHLD}; errno = 0; assert(!yasl_loop_watch_signals(l, sigs, 0, on_signal, &log) && errno == EINVAL); errno = 0; assert(!yasl_loop_watch_signals(l, sigs, 3, NULL, &log) && errno == EINVAL); assert(yasl_loop_watch_signals(l, sigs, 3, on_signal, &log)); /* One signal belongs to one loop at a time. */ errno = 0; assert(!yasl_loop_watch_signals(other, sigs, 1, on_signal, &log) && errno == EBUSY); /* Deliveries before the callback runs coalesce per signal. */ raise(SIGUSR1); raise(SIGUSR1); raise(SIGUSR2); assert(yasl_loop_step(l, 0) >= 1); assert(log.seen[SIGUSR1] == 1 && log.seen[SIGUSR2] == 1); assert(previous_handler_hits == 0); /* A signal sent from another thread wakes an indefinite wait. */ struct thread_ctx ctx = {.loop = l, .signo = SIGUSR1}; pthread_t t; assert(pthread_create(&t, NULL, wake_thread, &ctx) == 0); step_until(l, &log.seen[SIGUSR1], 2); pthread_join(t, NULL); /* SIGCHLD arrives on the loop thread when a child exits. */ pid_t child = fork(); assert(child >= 0); if (child == 0) _exit(0); step_until(l, &log.seen[SIGCHLD], 1); int status; assert(waitpid(child, &status, 0) == child); /* Replacing the watch restores the signals it drops. */ const int only_usr1[] = {SIGUSR1}; assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); raise(SIGUSR2); assert(previous_handler_hits == 1); assert(yasl_loop_watch_signals(other, (const int[]){SIGUSR2}, 1, on_signal, &log)); yasl_loop_unwatch_signals(other); assert(previous_handler_hits == 1); raise(SIGUSR2); assert(previous_handler_hits == 2); /* A callback may unwatch; the previous default action comes back. */ log.unwatch = true; raise(SIGUSR1); step_until(l, &log.seen[SIGUSR1], 3); struct sigaction now; assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); yasl_loop_unwatch_signals(l); /* Destroy unwatches. */ log.unwatch = false; assert(yasl_loop_watch_signals(l, only_usr1, 1, on_signal, &log)); yasl_loop_destroy(l); assert(sigaction(SIGUSR1, NULL, &now) == 0); assert(now.sa_handler == SIG_DFL); yasl_loop_destroy(other); struct sigaction dfl = {.sa_handler = SIG_DFL}; sigemptyset(&dfl.sa_mask); sigaction(SIGUSR2, &dfl, NULL); } int main(void) { signal(SIGALRM, on_alarm); alarm(10); test_post_order(); test_cross_thread(); test_signals(); printf("Loop tests passed.\n"); return 0; }