Commit 582c407
authored
Scalable Topics:
* st: received-message plumbing — MessageImpl + MessageCore accessors
The consumer receive path needs the impl side of detail::MessageCore, which was
declared but never defined (like ProducerCore was before the producer landed):
- lib/st/MessageImpl.h: pulsar::st::MessageImpl, a thin view over a classic
pulsar::Message (owns payload + metadata) plus the segment-qualified st
MessageId minted on receive, with an optional topic override for namespace mode.
- lib/st/MessageCore.cc: the out-of-line MessageCore accessors, forwarding to it.
All accessors map to public classic Message getters except sequenceId(), which the
classic public API does not expose; it returns -1 for now (a TODO to revisit with a
classic accessor when the Stream consumer needs it, rather than touch the classic
API here). Shared by all three consumer types.
* st: classic consumer segment seam — subscribeSegmentAsync
The scalable-topics queue/stream consumers attach a Shared consumer per active
segment, on the segment's segment:// backing topic — which the public subscribe
path rejects. Add ClientImpl::subscribeSegmentAsync, mirroring the producer's
createSegmentProducerAsync: the private single-topic subscribeToTopicsAsyncV2
gains an allowSegmentTopic flag (default false; the segment-domain rejection
becomes isSegment() && !allowSegmentTopic), and the new public method calls it
with true. A segment is a non-partitioned persistent topic, so it lands in the
single-ConsumerImpl branch of handleSubscribe unchanged. No broker pin (the Java
consumer path does not pin; the DAG-provided owner resolves via segment:// lookup).
* st: clang-format-11 line wrapping in segment seam + MessageImpl ctor
Wrap two over-length lines that clang-format-11 (the CI style) breaks but
clang-format-18 leaves on one line: the subscribeToTopicsAsyncV2 call in
ClientImpl::subscribeSegmentAsync and the MessageImpl constructor signature.
Formatting only, no behavior change.
* st: queue consumer core — per-segment fan-in over a mux receive queue
Implement the single-topic scalable-topics queue consumer (a port of the Java
v5 ScalableQueueConsumer). A Shared subscription is fanned across every segment
of the topic — active AND sealed, since a sealed segment may still hold
undrained messages — with one classic Shared-subscription pulsar::Consumer per
segment created through the ClientImpl::subscribeSegmentAsync seam.
- ReceiveQueue: a bounded fan-in mux. Per-segment receive loops offer() messages;
the user receiveAsync()es them in FIFO order. offer() returns a future that
completes only when the queue has room, so a slow consumer back-pressures the
underlying segment consumers' flow control rather than buffering unboundedly.
Timed receives fail with ResultTimeout; close() fails every waiter.
- QueueConsumerImpl: owns one DagWatchSession and the per-segment consumers.
Each segment loop stamps the segment id onto every message (MessageIdFactory)
and fans it into the shared queue. Acks/nacks route back to the owning
segment's consumer via the message id's segment id. Layout changes add
consumers for new segments and close ones that left the DAG; a segment that
reports TopicTerminated (a drained sealed segment) is closed and dropped.
- QueueConsumerCore: thin forwarders mapping MessageImplPtr to MessageCore.
- Wire ClientImpl::subscribeQueueAsync (was notImplementedYet), mirroring
createProducerAsync: build the impl, start(), then mint the public core.
Transactional acknowledge is not implemented yet (logged and dropped); the
dead-letter and namespace-subscription paths are deferred to later slices.
* Handle CommandReachedEndOfTopic on the consumer receive path
The classic client never handled BaseCommand::REACHED_END_OF_TOPIC (type 27):
handleIncomingCommand fell through to default: and closed the whole connection
as an "invalid message from server". Any consumer of a terminated topic — and
every scalable-topics queue consumer, which subscribes to sealed segments to
drain their backlog — would therefore churn its connection (close, reconnect,
re-subscribe, reach end of topic again) instead of learning the topic ended.
Handle it: dispatch REACHED_END_OF_TOPIC to the target consumer (mirroring
handleActiveConsumerChange), and have ConsumerImpl surface ResultTopicTerminated
on the async receive path once the prefetch queue drains — matching the Java
client, whose consumers close a drained sealed segment on TopicTerminated. The
broker only sends the command once the consumer's read position reaches the
terminate marker, so buffered messages always drain before termination.
Scope is the async receiveAsync path (what the scalable consumer uses); the
blocking sync receive() is unchanged (it would need to interrupt a parked
pop(), and a terminated topic there already behaves as "no more messages").
Adds ConsumerTest.testReceiveAsyncAfterTopicTerminated.
* st: queue consumer produce->consume e2e test
End-to-end coverage for the scalable-topics queue consumer against a real
broker, gated on PULSAR_ST_E2E (the broker-free unit run skips it):
- testProduceThenConsumeRoundTrip: produce 25 keyed messages, receive and ack
all of them through a Shared subscription, assert the payloads round-trip and
every received id carries a real segment id.
- testConsumeAcrossSplitSegments: over a topic pre-split into two active
segments, produce 60 keyed messages and assert they fan in from both segments
through the mux receive queue — the multi-segment path the queue consumer
exists for, and the case that exercises draining the sealed parent segment.
Both pass against apachepulsar/pulsar:5.0.0-M1. The CI wiring (docker-compose +
run-unit-tests.sh) that runs these lands with the producer-e2e harness.
* st: drive queue-consumer e2e split through the admin REST API
Mirror the producer e2e (#603): each queue-consumer e2e test now creates its
own fresh-named scalable topic — and, for the fan-in test, splits it — through
the admin REST API, instead of consuming harness-pre-created, CLI-pre-split
fixed topics (st-e2e-queue / st-e2e-queue-split). The tests are now
self-contained and keep working under the REST-driven harness, where nothing
is pre-arranged for them.
Links HttpHelper.cc into pulsar-st-tests for the makePut/makePostRequest calls.
* st: address #605 review — clang-tidy move + drained-segment re-subscribe
- The queue-consumer subscribe callback applied std::move to a pulsar::Consumer,
whose virtual destructor suppresses the move constructor, so the move bound to
the copy constructor: clang-tidy performance-move-const-arg, which failed the
Lint job. Copy the handle directly (a shared-impl copy), matching StProducerImpl.
- On ResultTopicTerminated the drained segment's consumer was erased, but the
sealed segment stays in the DAG, so the next layout reconcile re-subscribed it
and the broker redelivered its still-unacked messages as duplicates. Track
drained segments and skip re-subscribing them; prune the set when a segment
leaves the DAG.
* Terminated-topic completeness on the consumer: reconnect + sync receive
Two gaps in the CommandReachedEndOfTopic handling, from #605 review:
- hasReachedEndOfTopic_ was never cleared, so after a reconnect (which clears
the prefetch queue and re-sends flow permits) a receiveAsync landing before
redelivery arrived would report a stale ResultTopicTerminated — and the
scalable queue consumer would then drop the segment permanently. Termination
stops new publications, not redelivery of unacked messages: clear the flag on
each new broker session; the broker re-sends the command once the re-created
consumer's read position reaches the terminate marker again.
- The sync receive paths ignored the flag entirely: the untimed receive()
blocked forever on a drained terminated topic and the timed one returned
ResultTimeout. Both now fail fast with ResultTopicTerminated when the flag is
set and the queue is empty, agreeing with the async path. (A receive already
parked in pop() when the command arrives still waits — waking it would need
an interruptible queue.)
Extends ConsumerTest.testReceiveAsyncAfterTopicTerminated to assert both sync
overloads.
* st: queue consumer drain, robustness, and honesty fixes from #605 review
- Ack loss on drained sealed segments: end-of-topic only means the classic
prefetch queue drained — messages already fanned into the mux queue or held
by the application still need the segment consumer to route their acks.
Track outstanding (fanned-in minus acked/nacked) messages per segment and
defer the drain-close until the count reaches zero; the consumer stays in
the map for ack routing meanwhile.
- Receive-loop recursion: receiveAsync completes inline when a message is
prefetched and offer()'s future is already complete while the queue has
room, so the re-arm chain grew the stack once per message. Hop the re-arm
through the IO executor so the chain is a loop again.
- A segment subscribe that failed off the first-layout path was only retried
on the next DAG push, which may not come for hours: back it with a bounded
backoff retry (10 attempts, 100->500ms, the producer's constants), skipping
segments that left the DAG or drained.
- Message::topic() reported the internal segment:// backing topic; pass the
scalable topic as the override so the public contract holds.
- A configured deadLetterPolicy was silently ignored; it now fails the
subscribe with ResultOperationNotSupported, and the API docs say so, until
dead-lettering lands.
- ReceiveQueue timed receives never cancelled their timer when a message won
the race, accumulating live timers proportional to receive rate x timeout;
park {promise, timer} together and cancel on delivery and on close.
* st: e2e for draining a sealed segment's backlog with sticking acks
The scenario the queue consumer exists for — a split seals the parent WITHOUT
migrating its backlog — had no coverage: both existing e2e tests split before
producing, so the sealed parent was always empty and no end-of-topic was ever
delivered. Produce 1200 messages (more than the classic prefetch queue and the
mux capacity), split, then consume: every pre-split message must arrive through
the sealed parent, and reattaching a second consumer on the same subscription
must receive nothing — proving the acks routed through the drain-deferred
close instead of being dropped. Fails on the pre-fix code.
* Pin jidicula/clang-format-action to its commit SHA for the ASF actions policy
Since mid-August every new PR-validation run on this repo fails at workflow
startup (startup_failure, 0s, "workflow file issue") with no workflow change
on main — the same repo-wide pattern as the docker/build-push-action break
fixed by #602. The ASF GitHub Actions policy requires external actions to be
pinned to a specific git hash, and jidicula/clang-format-action@v4.11.0 was
the one remaining tag-pinned external action after #602 pinned the docker
ones. Pin it to the commit the v4.11.0 tag points to (f62da5e, unchanged
behavior).pulsar::st queue consumer — per-segment fan-in over a mux receive queue (#605)1 parent 4468ce4 commit 582c407
18 files changed
Lines changed: 1496 additions & 7 deletions
File tree
- include/pulsar/st
- detail
- lib
- st
- tests
- st
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
84 | | - | |
| 84 | + | |
| 85 | + | |
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
| |||
328 | 329 | | |
329 | 330 | | |
330 | 331 | | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
331 | 335 | | |
332 | 336 | | |
333 | 337 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| |||
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
| 64 | + | |
63 | 65 | | |
64 | 66 | | |
65 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
853 | 853 | | |
854 | 854 | | |
855 | 855 | | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
856 | 877 | | |
857 | 878 | | |
858 | 879 | | |
| |||
997 | 1018 | | |
998 | 1019 | | |
999 | 1020 | | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
1000 | 1025 | | |
1001 | 1026 | | |
1002 | 1027 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| 108 | + | |
108 | 109 | | |
109 | 110 | | |
110 | 111 | | |
| |||
265 | 266 | | |
266 | 267 | | |
267 | 268 | | |
| 269 | + | |
268 | 270 | | |
269 | 271 | | |
270 | 272 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
604 | 604 | | |
605 | 605 | | |
606 | 606 | | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
607 | 613 | | |
608 | | - | |
| 614 | + | |
| 615 | + | |
609 | 616 | | |
610 | 617 | | |
611 | 618 | | |
| |||
627 | 634 | | |
628 | 635 | | |
629 | 636 | | |
630 | | - | |
| 637 | + | |
631 | 638 | | |
632 | 639 | | |
633 | 640 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
106 | 114 | | |
107 | 115 | | |
108 | 116 | | |
| |||
203 | 211 | | |
204 | 212 | | |
205 | 213 | | |
206 | | - | |
| 214 | + | |
| 215 | + | |
207 | 216 | | |
208 | 217 | | |
209 | 218 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
337 | 337 | | |
338 | 338 | | |
339 | 339 | | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
340 | 345 | | |
341 | 346 | | |
342 | 347 | | |
| |||
823 | 828 | | |
824 | 829 | | |
825 | 830 | | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
826 | 849 | | |
827 | 850 | | |
828 | 851 | | |
| |||
1189 | 1212 | | |
1190 | 1213 | | |
1191 | 1214 | | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
1192 | 1223 | | |
1193 | 1224 | | |
1194 | 1225 | | |
| |||
1217 | 1248 | | |
1218 | 1249 | | |
1219 | 1250 | | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
1220 | 1258 | | |
1221 | 1259 | | |
1222 | 1260 | | |
| |||
1247 | 1285 | | |
1248 | 1286 | | |
1249 | 1287 | | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
1250 | 1292 | | |
1251 | 1293 | | |
1252 | 1294 | | |
| |||
1255 | 1297 | | |
1256 | 1298 | | |
1257 | 1299 | | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
1258 | 1304 | | |
1259 | 1305 | | |
1260 | 1306 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
106 | 109 | | |
107 | 110 | | |
108 | 111 | | |
| |||
185 | 188 | | |
186 | 189 | | |
187 | 190 | | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
188 | 195 | | |
189 | 196 | | |
190 | 197 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
0 commit comments