Add notifly::exchange, fix post_and_wait's double-answer bug - #12
Merged
Conversation
post_and_wait() covers one request/reply shape: post one notification, wait
for one reply, take the first that arrives. Real protocols keep producing
shapes it cannot express, so callers hand-roll subscribe/post/wait/unsubscribe
around add_observer() and have to get the ordering right themselves.
notifly::exchange is that pattern as a scoped object. Handlers return a
notifly_verdict per delivery -- skip, keep or done -- which is what lets one
primitive cover the rest:
- skip: ignore a delivery and stay subscribed, for a sender that reports the
state it is leaving before the one it is entering. Taking the first reply
there reports the wrong state.
- several on() calls: wait on alternatives at once; wait() returns which
notification answered.
- keep + drain(quiet, deadline): a reply streamed in pieces whose length the
protocol never states.
- silent_for(window): protocols where the sender only speaks up to refuse, so
silence is the successful outcome.
- subscribe and wait() without posting: waiting on an external event rather
than on a command.
post_and_wait() is now a wrapper over it, unchanged in signature and behaviour
except that it no longer throws when a sender answers twice: the response
observer called promise->set_value() unguarded, and the observer was only
removed after the wait returned, so a second delivery in that window raised
std::future_error out of the dispatch loop -- which runs with the centre
locked and no try/catch, so it surfaced on the poster's thread. An exchange
ignores deliveries after the first done, so the guard now applies everywhere.
The private is_tuple trait moves to notifly_detail so exchange::capture() can
use it; add_response_observer() goes away with its only caller.
Tests cover each shape plus lifetime, subscription errors and the
double-answer regression (16 new, 54 total green). example/exchange_example.cpp
walks a simulated device through all five shapes.
capture() is what post_and_wait() is built on, and it's public on its own -- useful shorthand for a branch of a multi-alternative exchange that just needs the payload, no handler. It had no test that exercised it directly (only indirectly, through post_and_wait()'s own tests) and no mention outside the doxygen comment on the declaration. Add both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
notifly::exchange, the subscribe/wait primitivepost_and_wait()is now built on top of,and fixes a real bug in
post_and_wait()along the way: a second answer from a sender that repliestwice used to call
promise->set_value()twice, throwingstd::future_error.exchangeholds itsmutex across the "first done wins" decision, so that can't happen.
post_and_wait()covers one shape: post one notification, wait for one reply, take the first thatarrives.
exchangecovers what that shape can't express — several possible answers where which onefired matters, ignoring a delivery that isn't the one being waited for, a reply streamed in pieces of
unstated length, treating silence as success, or waiting on something no command asked for. See the
new "Waiting For A Reply" section in the README for the full shape table and
capture(), theconvenience
on()wraps for a plain value or tuple readback.Tests
56 tests (30 existing
notifly+ 8NotiflyMemoryLeak, both untouched, + 16 newexchangetests +2 for
capture()added while reviewing this before tagging).example/exchange_example.cppdrives asimulated device through each shape in the table.
Version
NOTIFLY_VERSION_{MAJOR,MINOR,PATCH}ininclude/notifly.his already at 3.6.0. Taggingv3.6onthis merge commit.
🤖 Generated with Claude Code