Six foxglove_bridge-parity features: whitelist, QoS depths, topic push, backpressure, latched replay, TLS#7
Open
facontidavide wants to merge 16 commits into
Open
Six foxglove_bridge-parity features: whitelist, QoS depths, topic push, backpressure, latched replay, TLS#7facontidavide wants to merge 16 commits into
facontidavide wants to merge 16 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under RMWs that don't propagate publisher history depth through discovery (e.g. rmw_fastrtps_cpp, the default), every publisher reports depth 0, so the aggregated total clamped to min_qos_depth and produced depth-1 subscription queues that drop messages on high-rate topics. Treat a total of 0 as "unknown" and keep the historical default of min(100, max_qos_depth) instead. Also declare rmw_cyclonedds_cpp as a test_depend (the depth aggregation tests force it, and apt-based CI containers don't ship it), and fix include ordering in test_whitelist_filter.cpp so pre-commit is clean repo-wide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting sum Replace the total==0 special case with a uniform per-publisher rule: a publisher reporting depth 0 (RMW didn't propagate depth, or KEEP_ALL) contributes the fallback of 100 instead of 0, so unknown depths never shrink the queue and mixed known/unknown topics aggregate sensibly. Make the sum saturating (each contribution capped at max_qos_depth, running total clamped to max_qos_depth) so it can never wrap size_t. Tests: add a mixed KEEP_ALL + depth-10 publisher case, and a runtime RMW probe that skips the depth-assertion tests when the active RMW doesn't propagate publisher history depths through discovery (protects against a harness overriding the forced CycloneDDS selection). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Topics appearing between server initialization and the first topic_poll_interval tick were folded into the silent baseline and never notified. Snapshot immediately after initialize() instead, before any client can connect. (Codex review finding on 866371d.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oof depth sum) - send_binary: re-check client existence under clients_mutex_ before enqueueing an over-watermark frame; a concurrent disconnect between the socket-handle lookup and the enqueue could otherwise recreate a pending queue for a gone client, stranding the frame until shutdown and skewing dropped_frame_count(). Return false (same contract as the initial lookup miss) instead. - Document that backlog flushing only happens inside send_binary by design (bounded queue, fresh-data-first plotting clients). - generic_subscription_manager: replace add-then-clamp saturating depth sum with headroom arithmetic so size_t wrap is structurally impossible even for extreme max_qos_depth_ values via the constructor API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Send the replay binary frame strictly AFTER the subscribe response: handle_subscribe now queues serialized frames in pending_replays_ (new leaf replays_mutex_); process_single_request flushes them right after send_reply succeeds, and drops them when the reply fails or the session is cleaned up. The client always has the schema before the frame. - Clear latched state when the last subscription ref is released (new release_subscription_ref helper used by unsubscribe/pause/cleanup/ rollback, backed by SubscriptionManagerInterface::is_subscribed with a default-false implementation and a ROS2 forwarding override): a stale retained sample is never replayed ahead of the fresh DDS transient_local redelivery on the next subscription. - Skip the replay while the retained sample is still in the normal aggregation buffer (MessageBuffer::get_latched_for_replay): the regular publish cycle delivers it to the new subscriber anyway, so replaying would duplicate it. - docs/API.md: document teardown, duplicate gate, and that replay frames bypass rate limiting and publish statistics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- A client that subscribes to a latched (transient_local) topic while paused acquires no middleware ref, so it got neither the replay frame nor DDS redelivery when resume joined an already-shared subscription. handle_resume now runs the same latched bookkeeping as handle_subscribe after each successful re-subscribe; the frame is flushed by process_single_request right after the resume response, preserving the response-before-frame ordering. - Factor the shared logic into BridgeServer::collect_latched_replay (set_latched + get_latched_for_replay + serialize + queue under the leaf replays_mutex_), replacing handle_subscribe's two-phase replay_candidates machinery; document the benign queue-vs-cleanup race there. - docs/API.md (TLS): note that only cert/key readability is validated at startup — a mismatched pair only surfaces as per-connection handshake failures in the server log (IXWebSocket defers TLS setup to accept time). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a session died server-side (heartbeat timeout) while the socket stayed open, WebSocketMiddleware's pending_frames_/last_drop_warn_ entries for that client survived: a timed-out slow client parked up to client_backlog_size frames indefinitely, and stale frames could flush into a new logical session on the same connection. - MiddlewareInterface: new drop_pending(client) hook, default no-op. - WebSocketMiddleware: override folds the client's dropped_total() into the lifetime counter and erases its queue + warn-throttle entry (the socket itself is untouched). - BridgeServer::cleanup_session: call drop_pending after the cleanup_mutex_ scope (middleware calls must not run under that lock). - docs/API.md: correct backlog flush wording (delivered on next send attempt, no background flush), note frames in flight may arrive after an unsubscribe response, document latched replay on resume and that replay frames share the backpressure queue. Co-Authored-By: Claude Fable 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
Closes the feature gap with foxglove_bridge on the topic-subscription feature set, per the side-by-side comparison. All protocol changes are additive —
protocol_versionstays 1; existing clients are unaffected.get_topics,subscribe, and topic-change detectiontopic_whitelist(ROS2) /--topic-whitelist(CLI)min_qos_depth,max_qos_depthsubscribe_topic_updatescommand +topics_changednotification; eager baseline snapshot at startuptopic_poll_interval(0 disables)bufferedAmount(); never disconnects; queues cleared on disconnect and session-timeoutclient_backlog_sizewss://— OpenSSL server certificate viaix::SocketTLSOptions;PJ_BRIDGE_TLSCMake option for the FetchContent pathtls,certfile,keyfile/--certfile,--keyfileLogic adapted from foxglove_bridge (QoS depth rule, lossy-send policy) carries MIT attribution comments; MIT is compatible with this repo's AGPL-3.0.
Design spec and implementation plan are included under
docs/superpowers/.Review process
Every feature commit passed a dual review (Claude + Codex); the six
fix:commits are the review findings, including a FastRTPS KEEP_LAST(1) regression, a TSAN lock-order inversion, an orphan-queue disconnect race, replay-before-schema ordering, stale latched samples across teardown, and middleware queue leakage across session timeouts.Test plan
pixi run -e humble build && pixi run -e humble test— 233 tests, 0 failures (baseline 177)pre-commit run -acleanNote for reviewers: the depth tests force
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp(with a probe-and-skip fallback) because FastRTPS reports depth 0 through discovery;package.xmlgained<test_depend>rmw_cyclonedds_cpp</test_depend>so the apt CI legs install it.🤖 Generated with Claude Code