feat: add Watchable::modify for atomic read-modify-write#53
Conversation
Callers previously had to reach for set, which cannot observe the current value. modify holds the write lock across a user closure, making compare-and-swap and other read-modify-write patterns expressible without a second synchronization primitive. Change detection uses Eq, so watchers are only notified when the value actually changes. The closure's return value is forwarded to the caller, which keeps CAS-style code direct. The name avoids a collision with Watcher::update, which refreshes a watcher's cached value and is a separate concept.
|
Documentation for this PR has been generated and is available at: https://n0-computer.github.io/n0-watcher/pr/53/docs/n0_watcher/ Last updated: 2026-04-17T11:56:16Z |
matheus23
left a comment
There was a problem hiding this comment.
I'm somewhat hesitant on introducing this...
Previously all the locking was contained to the library and just an implementation detail.
I've considered switching away from the RwLock entirely and switching to an ArcSwap instead!
Adding this API might make this impossible/expose the implementation detail.
It also means that now it's possible to deadlock yourself with the watcher API, which I don't totally like either.
Soooo I'm unsure how to proceed.
Perhaps I'd be happier with providing operations that look more similar to atomic operations, so e.g. actually implementing a compare-and-swap operation. We already require T: Eq, after all.
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_modify_cas() { |
There was a problem hiding this comment.
Can you spell out CAS (I had to think a bit about what it meant)
| async fn test_modify_cas() { | |
| async fn test_modify_compare_and_swap() { |
Adds
Watchable::modify, which runs a caller-supplied closure under thewrite lock so read-modify-write patterns, including compare-and-swap,
can be expressed directly. Previously the only writer API was
set,which cannot observe the current value.
The closure takes
&mut Tand may return anyR, which is forwarded tothe caller. Change detection uses
Eqon the pre- and post-closurevalues, so watchers are only woken on real changes and the epoch is only
bumped then.
Reason: This is a TOCTOU race: https://github.com/n0-computer/iroh/blob/main/iroh/src/socket/transports/relay/actor.rs#L931