Skip to content

Commit 832a337

Browse files
committed
relay: move LF forwarder ownership to the publisher exec, off the registry
In LocalForwarder mode the publisher forwarder is owned by tlForwarders_ on the publisher exec; clear the registry's ref once that ownership transfers. Callers resolve the forwarder by FullTrackName from the publisher exec rather than holding a registry ref, and duplicate-publisher eviction drains the old forwarder on its own exec (via the displaced same-exec ref or tlForwarders_).
1 parent 9bc3b55 commit 832a337

6 files changed

Lines changed: 329 additions & 73 deletions

File tree

docs/dev/local-forwarder-flow.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,13 @@ Executor legend used below:
4848

4949
## Publish
5050

51-
A publisher's local forwarder **is** the publisher forwarder — the same object serves as the
52-
on-thread data-plane forwarder and as the registry's publisher entry (no second forwarder is
53-
constructed on `relayExec_`, unlike the `RelayExec`-mode branch in `publishWithSession`).
51+
A publisher's local forwarder **is** the publisher forwarder — a single forwarder serves the
52+
whole track, living on the publisher's executor rather than on `relayExec_`. Ownership lives in
53+
`tlForwarders_` on the publisher's executor, **not** in the registry: once registration
54+
completes, `registerPublishOnRelayExec` calls `registry_.clearForwarder(ftn)`, so the registry
55+
entry keeps its subscription/namespace bookkeeping but drops its forwarder ref. Code that needs
56+
the forwarder resolves it by `FullTrackName` from `tlForwarders_` on the owning executor, rather
57+
than holding a registry ref.
5458

5559
### Setup
5660

@@ -65,7 +69,8 @@ constructed on `relayExec_`, unlike the `RelayExec`-mode branch in `publishWithS
6569
[Relay] └─▶ registerPublishOnRelayExec()
6670
[Relay] ├─▶ publishWithSession() # registry + namespace tree
6771
[Relay] │ └─▶ addSubscriberAndPublish() # attach existing SUBSCRIBE_NAMESPACE subs (see Publish fan-out)
68-
[Relay] └─▶ relayChainFilter->setDownstream(topNFilter)
72+
[Relay] ├─▶ relayChainFilter->setDownstream(topNFilter)
73+
[Relay] └─▶ registry_.clearForwarder(ftn) # drop registry ref; tlForwarders_ owns the fwd
6974
[Pub] return {consumer, replyTask} # immediate
7075
```
7176

@@ -133,11 +138,13 @@ upstream work, and nests a single sortie to `[Pub]` to wire the channel sub.
133138
[Sub] ├─▶ localFwd->addSubscriber() # so `forward` is right pre-hop
134139
[Sub] └─▶ ⇢⇢▶ [Relay] attachNewLocalForwarderOnRelayExec()
135140
[Relay] ├─▶ joinOrPrepareUpstreamSubscription() # registry: first vs subsequent
136-
[Relay] ├─▶ buildLocalToPublisherCallbacks()
137141
[Relay] └─▶ ⇢⇢▶ [Pub] single sortie:
142+
[Pub] ├─ if SUBSEQUENT: resolve live publisherFwd from tlForwarders_
143+
[Pub] ├─▶ buildLocalToPublisherCallbacks()
138144
[Pub] ├─ if FIRST subscriber:
139145
[Pub] │ └─▶ installPublisherForwarderCallbackChain() # chain + tlForwarders_ slot
140146
[Pub] ├─▶ installChannelSubscriber(localFwd ↔ publisherFwd)
147+
[Pub] ├─ if SUBSEQUENT: read seed largest/extensions
141148
[Pub] └─ if FIRST subscriber:
142149
[Pub] ├─▶ addChannelSubscriber(relayChain, passive)
143150
[Pub] └─▶ subscribeUpstreamAndApplyOk()
@@ -147,7 +154,7 @@ upstream work, and nests a single sortie to `[Pub]` to wire the channel sub.
147154
[Sub] ◀── back on subscriberExec (tail)
148155
[Sub] ├─ sawOnEmpty? ─▶ teardownLocalForwarderOnFailure()
149156
[Sub] ├─ error? ─▶ publishDone + remove
150-
[Sub] ├─▶ apply upstreamOk extensions / largest
157+
[Sub] ├─▶ apply largest/extensions (upstreamOk if FIRST, else seeded)
151158
[Sub] ├─▶ replayPendingFowarderEvents() # drain buffered events
152159
[Sub] └─▶ return sub
153160
```
@@ -158,12 +165,16 @@ publisher-forwarder control chain and claims the `tlForwarders_` slot via
158165
`installPublisherForwarderCallbackChain` — the **same** wiring the publish path uses, so a
159166
subscribe-initiated publisher forwarder is symmetric with a publish-initiated one. Subsequent
160167
subscribers on the same thread hit the `attachSubscriber` fast path (now also for
161-
subscribe-initiated tracks); on other threads they take only the `installChannelSubscriber` half
162-
of the sortie.
168+
subscribe-initiated tracks); on other threads they take the non-first half of the sortie:
169+
resolve the live publisher forwarder from `tlForwarders_` on the publisher exec, install the
170+
channel sub, and seed `largest`/extensions from it.
163171

164172
The forwarder is created on `relayExec_` (in `joinOrPrepareUpstreamSubscription`) with a **null**
165173
callback, then gets its real callback + tl slot installed on `[Pub]` in the first-subscriber
166-
sortie — `tlForwarders_.get()` must run on the forwarder's own exec. It uses `removeOnEmpty=true`
174+
sortie — `tlForwarders_.get()` must run on the forwarder's own exec. Once that sortie claims the
175+
tl slot, the relay-exec continuation calls `registry_.clearForwarder(ftn)`, transferring ownership
176+
to `tlForwarders_` and leaving the registry with only bookkeeping — the same as the publish path.
177+
It uses `removeOnEmpty=true`
167178
(subscribe-initiated tracks drop on empty, unlike publish): `LocalForwarderCallback` vacates the
168179
tl slot on `[Pub]` when the last subscriber leaves, while `onEmptyImpl` unsubscribes upstream and
169180
removes the registry entry on `[Relay]`.

0 commit comments

Comments
 (0)