summaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 16:55:45 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 16:55:45 +0300
commitefca84e8b5204c4d5ce28113474df0bf0b67dbf3 (patch)
tree8ab6ed74f1e29b3a9dd3f1b2cb031c81891a5902 /include
parent62717de20d2a0c8d3596a9223e56e734971e3ec5 (diff)
Wake and post to loops from any thread, and watch signals
wsap_term runs its own eventfds to wake the loop from its pty worker and accessibility threads, and its own signalfd or self-pipe per platform. yasl_loop_kick() is now documented as safe from any thread and from a signal handler. yasl_loop_post() queues a function to run on the owning thread during a later step. yasl_loop_watch_signals() installs a process-wide SA_RESTART handler per signal that records the signal and kicks the loop, so callbacks run on the loop thread on every platform without touching thread masks. Cocoa dispatch waited only for AppKit events, so kicks, posts, and fd watches could not end an indefinite wait, and yasl_post_event() did not wake yasl_run(). While waiting, Cocoa now mirrors the loop's descriptors in a kqueue whose run loop source posts a wake event. If kqueue rejects a descriptor, the wait is bounded to 10 ms. The Cocoa change is not built or run here; it needs a macOS check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'include')
-rw-r--r--include/yasl/app.h3
-rw-r--r--include/yasl/loop.h63
2 files changed, 59 insertions, 7 deletions
diff --git a/include/yasl/app.h b/include/yasl/app.h
index bf6de9c..b7e1c13 100644
--- a/include/yasl/app.h
+++ b/include/yasl/app.h
@@ -98,7 +98,8 @@ YASL_API void yasl_surface_request_frame(struct yasl_surface *s);
* @param code Application-defined message code (yasl_user_event.code).
* @param payload Arbitrary pointer passed through to the event.
* @param size Optional payload size.
- * @return true if queued successfully, false if queue is full or memory failed.
+ * @return true if queued; false if memory failed or d is not run by yasl_run().
+ * Applications that run their own loop use yasl_loop_post() instead.
*/
YASL_API bool yasl_post_event(struct yasl_display *d, struct yasl_surface *s,
uint32_t code, void *payload, size_t size);
diff --git a/include/yasl/loop.h b/include/yasl/loop.h
index 9faa8f7..a8e9974 100644
--- a/include/yasl/loop.h
+++ b/include/yasl/loop.h
@@ -3,7 +3,8 @@
* Optional event loop, file watches, and timers.
*
* Use the loop's owning thread for all calls and callbacks, except
- * yasl_loop_now_ns(). A display borrows a supplied loop. For window applications,
+ * yasl_loop_now_ns(), yasl_loop_kick(), and yasl_loop_post(), which any thread
+ * may call. A display borrows a supplied loop. For window applications,
* drive yasl_display_dispatch(), drain input, then yasl_display_flush(); the
* standalone loop_run() function does not replace this cycle. Timer intervals are
* nanoseconds; timeout_ms arguments are milliseconds. Do not recursively step/run
@@ -29,10 +30,11 @@ extern "C" {
*/
YASL_API struct yasl_loop *yasl_loop_create(void);
/**
- * Release a loop, its watches, and its timers.
+ * Release a loop, its watches, its timers, and its signal watch.
* @param l Loop, or NULL for no action.
* Close displays borrowing this loop first. Must not run during a loop callback
- * or an active step/run. Watched file descriptors are not closed.
+ * or an active step/run. Watched file descriptors are not closed. Posted
+ * functions that have not run are dropped; no thread may still post or kick.
*/
YASL_API void yasl_loop_destroy(struct yasl_loop *l);
/**
@@ -59,13 +61,62 @@ YASL_API int yasl_loop_step(struct yasl_loop *l, int timeout_ms);
*/
YASL_API void yasl_loop_quit(struct yasl_loop *l);
/**
- * Signal the loop's wake pipe without dispatching callbacks.
+ * Wake a loop that is waiting, without running any callback.
* @param l Loop, or NULL for no action.
- * Multiple signals may coalesce. This does not schedule a redraw or enqueue an
- * event, and it does not provide a cross-thread application messaging contract.
+ * May be called from any thread and from a signal handler. The loop must stay
+ * alive until the call returns. A waiting yasl_loop_step(), yasl_loop_run(), or
+ * yasl_display_dispatch() on this loop returns early; a loop that is not
+ * waiting returns early from its next wait instead. Kicks coalesce. This does
+ * not schedule a redraw or enqueue an event; use yasl_loop_post() to run code
+ * on the owning thread.
*/
YASL_API void yasl_loop_kick(struct yasl_loop *l);
/**
+ * Run a function on the loop's owning thread.
+ * @param l Loop.
+ * @param fn Required function, called once with ud during a later step.
+ * @param ud Borrowed userdata until fn runs.
+ * @return true if queued; false for NULL arguments, a loop without a wake
+ * pipe, or allocation failure.
+ * May be called from any thread, but not from a signal handler. Wakes the loop
+ * like yasl_loop_kick(). Functions run in posting order, during a
+ * yasl_loop_step() or display dispatch on the owning thread. They may post
+ * again; a function posted while posts are running waits for a later step.
+ * Stop posting before yasl_loop_destroy(), which drops functions that have
+ * not run.
+ */
+YASL_API bool yasl_loop_post(struct yasl_loop *l, void (*fn)(void *ud), void *ud);
+/**
+ * Deliver POSIX signals to a callback on the loop's owning thread.
+ * @param l Loop.
+ * @param signals Signal numbers to watch; the array is read during the call.
+ * @param n Number of entries, at least one.
+ * @param cb Required callback, given ud and one signal number per delivery.
+ * @param ud Borrowed userdata until the watch is removed.
+ * @return true if every signal is watched; false with errno set and no
+ * dispositions changed. EINVAL means a NULL or invalid argument, including
+ * SIGKILL and SIGSTOP. EBUSY means another loop watches one of the signals.
+ * Replaces this loop's previous watch, if any. Installs one process-wide
+ * handler per signal with SA_RESTART and an empty handler mask, and saves the
+ * previous action. The handler only records the signal and wakes the loop,
+ * so it may run on any thread. YASL does not change any thread's signal mask:
+ * a signal blocked in every thread stays pending and is not delivered. Caught
+ * signals reset to their default action across exec, so spawned programs
+ * do not inherit the handler. Repeated deliveries of one signal before the
+ * callback runs coalesce into one callback. SIGCHLD reports stopped and
+ * continued children as well as exits. Callbacks run during yasl_loop_step()
+ * or display dispatch, and may remove the watch.
+ */
+YASL_API bool yasl_loop_watch_signals(struct yasl_loop *l, const int *signals, int n,
+ void (*cb)(void *ud, int signo), void *ud);
+/**
+ * Stop watching signals and restore their previous actions.
+ * @param l Loop, or NULL for no action.
+ * Does nothing if the loop watches no signals. Signals that arrived but have
+ * not reached the callback are dropped. yasl_loop_destroy() also unwatches.
+ */
+YASL_API void yasl_loop_unwatch_signals(struct yasl_loop *l);
+/**
* Read the monotonic clock used by YASL timers and timing results.
* @return Nanoseconds from CLOCK_MONOTONIC, unrelated to wall-clock time.
* May be called from any thread.