Skip to content

Introduce MainloopWaker for thread-safe pa_mainloop_wakeup#67

Open
orestisfl wants to merge 1 commit intojnqnfe:masterfrom
orestisfl:add-mainloop-waker
Open

Introduce MainloopWaker for thread-safe pa_mainloop_wakeup#67
orestisfl wants to merge 1 commit intojnqnfe:masterfrom
orestisfl:add-mainloop-waker

Conversation

@orestisfl
Copy link
Copy Markdown

the standard Mainloop is !Send (it's wrapped in Rc), so today the only way for downstream code to interrupt a blocking Mainloop::iterate(true) from another thread is to reach into _inner, grab the raw *mut pa_mainloop, and invoke pa_mainloop_wakeup via hand-rolled unsafe extern "C". that single C function is documented as thread-safe (it just writes a byte to the mainloop's internal self-pipe), so a small safe abstraction is worthwhile.

  • MainloopWaker: a Send + Sync + Clone handle wrapping the call.
  • Mainloop::waker(): hands out a waker tied to this mainloop.

the handle stores the mainloop pointer behind a
Mutex<*mut MainloopInternal>. MainloopWaker::wakeup holds the mutex across pa_mainloop_wakeup; Mainloop::drop nulls the pointer under the same mutex before the underlying pa_mainloop is freed. wakers therefore never observe a freed pointer and become no-ops once the mainloop is gone.

Rc<MainloopInner> is left unchanged: the rest of the standard mainloop API is !Send by design, so the Arc-backed sidecar is scoped to the one operation that genuinely is cross-thread.

Came up in JakeStanger/ironbar#875

the standard `Mainloop` is `!Send` (it's wrapped in `Rc`), so today the
only way for downstream code to interrupt a blocking
`Mainloop::iterate(true)` from another thread is to reach into `_inner`,
grab the raw `*mut pa_mainloop`, and invoke `pa_mainloop_wakeup` via
hand-rolled `unsafe extern "C"`. that single C function is documented as
thread-safe (it just writes a byte to the mainloop's internal self-pipe),
so a small safe abstraction is worthwhile.

* `MainloopWaker`: a `Send + Sync + Clone` handle wrapping the call.
* `Mainloop::waker()`: hands out a waker tied to this mainloop.

the handle stores the mainloop pointer behind a
`Mutex<*mut MainloopInternal>`. `MainloopWaker::wakeup` holds the mutex
across `pa_mainloop_wakeup`; `Mainloop::drop` nulls the pointer under the
same mutex before the underlying `pa_mainloop` is freed. wakers therefore
never observe a freed pointer and become no-ops once the mainloop is gone.

`Rc<MainloopInner>` is left unchanged: the rest of the standard mainloop
API is `!Send` by design, so the `Arc`-backed sidecar is scoped to the one
operation that genuinely is cross-thread.

Came up in JakeStanger/ironbar#875
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant