docs(054): revise after the pull-model prototype - #272
Merged
Merged
Conversation
A throwaway prototype built §4 against main (core API, both MQTT backends, the WebSocket server's outbound path, the embedded write ring) and measured it. Every allocation target held; the doc changes where the code disagreed. - §4.2: the lending poll_next cannot be wrapped in poll_fn (captured variable cannot escape FnMut), so neither next() nor a select arm compiled. Pull in two steps: poll_stage, then take_staged. Re-poll a route after a skip or lag so its waker stays registered; Ready(None) is final and must disarm a select arm; Tokio's coop budget makes Pending not mean empty. RouteId is a usize, RouteInfo carries the route's topic and payload capacities, RouteStats counts skips, the message type is OutboundMessage (Outbound is taken), and OutboundRoutes is built in build(). - §4.3: with_topic_fn beside with_topic_writer, since an unannotated closure fails inference (E0282) under a generic TopicWriter bound; From<fmt::Error> for TopicOverflow; overflow detected by TopicBuf even if the writer ignores the error. - §4.7: an empty bipbuffer whose pointers have moved only guarantees about half its capacity, so max_frame + reserve must fit capacity / 2. The has_room gate is a probe grant re-checked on every poll; PUBACKs wait for room (the reserve covers one packet) and are encoded before state.receive (Puback borrows the state); two AtomicWakers replace "polling notifier only"; Ready(None) is latched, or an inbound-only session never yields. - §5/§5.1: prototype measurements. Embedded MQTT round trip 9 -> 0 allocations (302 B -> 0), 58.6 -> ~49 us on loopback; native 16 -> 11; wake-up cost about 80 ns per idle SPMC route on the host; new outbound_next_parked row. - §4.6, §6, §7, §8: outage semantics confirmed on Tokio buffers; the no-compatibility decision supported (an ignored with_topic_provider made ten WebSocket tests time out instead of failing to compile); two more rejected alternatives; open questions updated, TCP_NODELAY noted as out of scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXDnQEx2THUSTASYkThxCw
2 of 5 tasks
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.
Description
Revises design 054 (zero-allocation connector boundary) with what a throwaway prototype of the pull model showed. The prototype built §4 against
mainatdfe6adc:InboundDispatch,OutboundRoutesandTopicWriterin core, both MQTT backends, the WebSocket server's outbound path, and the embedded write ring. It was measured but not merged. This PR changes only the design doc.Every allocation target in §5 held. The doc changes where the code disagreed with it:
§4.2 outbound pull. The doc defined
next()aspoll_fn(|cx| self.poll_next(cx)), and that does not compile:poll_nextreturns a borrow of its state, and apoll_fnclosure cannot return one ("captured variable cannot escapeFnMutclosure body"). The same applies to the select arm in §4.7. The pull now takes two steps,poll_stagethentake_staged. New rules:Ready(None)is final, and a select arm must be disabled after it;Pendingdoes not mean every route is empty, because Tokio's cooperative budget can return it with values still ready.§4.2 API details.
RouteIdis a plainusize.RouteInfocarries each route's topic and payload capacities.RouteStatscounts skips. The message type isOutboundMessage, becauseaimdb_core::Outboundalready exists.OutboundRoutesis built inbuild().§4.3 topic writer. The doc's closure example fails type inference (E0282) under a generic
TopicWriterbound, so the closure form gets its own method,with_topic_fn, besidewith_topic_writer.impl From<fmt::Error> for TopicOverflowis added. Overflow is detected byTopicBufitself, so a writer that ignores the error still has its value skipped.§4.7 embedded write ring.
capacity / 2, not "the ring minus the reserve".has_roomis a probe grant, re-checked on every poll of the arm.state.receive, because the PUBACK borrows the client state.Ready(None)is latched. Without that, a session with only inbound links never yields; the idle-session test saw 0 pings.§5 / §5.1 measurements.
main)rumqttc), one round tripA wake-up costs about 80 ns per idle SPMC route on the host. §5 gains a new
outbound_next_parkedrow.§4.6, §6, §7, §8.
with_topic_providerstill existed, ten WebSocket tests timed out instead of failing to compile.TCP_NODELAY, which is out of scope for this design.The prototype's code and its full review page (verdicts, test runs, patch) are not part of this PR. Review page: https://claude.ai/artifact/27gTstCTf72gB7gVmuy8wZ (private until its owner shares it).
Related Issue
Checklist
make check). (Not run for this docs-only change. The prototype's suites passed on its own branch; see §5.1.)🤖 Generated with Claude Code
https://claude.ai/code/session_01HXDnQEx2THUSTASYkThxCw
Generated by Claude Code