Skip to content

feat: add Watchable::modify for atomic read-modify-write#53

Open
Frando wants to merge 1 commit into
mainfrom
Frando/watchable-update
Open

feat: add Watchable::modify for atomic read-modify-write#53
Frando wants to merge 1 commit into
mainfrom
Frando/watchable-update

Conversation

@Frando
Copy link
Copy Markdown
Member

@Frando Frando commented Apr 17, 2026

Adds Watchable::modify, which runs a caller-supplied closure under the
write 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 T and may return any R, which is forwarded to
the caller. Change detection uses Eq on the pre- and post-closure
values, 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

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.
@github-actions
Copy link
Copy Markdown

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

@Frando Frando requested a review from matheus23 April 20, 2026 07:13
Copy link
Copy Markdown
Member

@matheus23 matheus23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/lib.rs
}

#[tokio::test]
async fn test_modify_cas() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you spell out CAS (I had to think a bit about what it meant)

Suggested change
async fn test_modify_cas() {
async fn test_modify_compare_and_swap() {

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.

2 participants