@@ -458,20 +458,18 @@ MoqxRelay::validatePublishNamespace(const FullTrackName& ftn, RequestID requestI
458458// source ends, so a chain over a forwarder that does not hold it has nothing to remove.
459459LocalForwarderRegistry::Claim MoqxRelay::installPublisherForwarder (
460460 const FullTrackName& ftn,
461- const std::shared_ptr<MoQForwarder>& fwd
461+ const std::shared_ptr<MoQForwarder>& fwd,
462+ bool removeOnEmpty
462463) {
463- // removeOnEmpty=false: the publisher's forwarder must survive subscriber churn.
464+ auto & localReg = localRegistry ();
464465 auto relayAdapter = std::make_shared<WeakRelayForwarderCallback>(weak_from_this ());
465466 auto crossExec =
466467 std::make_shared<CrossExecForwarderCallback>(relayExec_, fwd, std::move (relayAdapter));
467- fwd->setCallback (std::make_shared<LocalForwarderCallback>(
468- tlForwarders_.get (),
469- ftn,
470- std::move (crossExec),
471- /* removeOnEmpty=*/ false
472- ));
468+ fwd->setCallback (
469+ std::make_shared<LocalForwarderCallback>(&localReg, ftn, std::move (crossExec), removeOnEmpty)
470+ );
473471
474- return tlForwarders_-> replace (ftn, fwd);
472+ return localReg. replace (ftn, fwd);
475473}
476474
477475// Called from LocalPublishFilter::publish() on publisherExec. Creates the publisher's
@@ -485,12 +483,8 @@ Subscriber::PublishResult MoqxRelay::publishFromPublisherExec(
485483 return folly::makeUnexpected (std::move (*err));
486484 }
487485
488- if (!tlForwarders_.get ()) {
489- tlForwarders_.reset (new LocalForwarderRegistry ());
490- }
491-
492486 auto localPubFwd = std::make_shared<MoQForwarder>(pub.fullTrackName );
493- installPublisherForwarder (pub.fullTrackName , localPubFwd)
487+ installPublisherForwarder (pub.fullTrackName , localPubFwd, /* removeOnEmpty= */ false )
494488 .markReady (InitialTrackState{pub.largest , pub.extensions });
495489
496490 // crossExecFilter is a channel subscriber for the relay exec
@@ -1137,6 +1131,10 @@ folly::coro::Task<void> MoqxRelay::addSubscriberAndPublishViaLocalForwarder(
11371131 );
11381132
11391133 auto [localFwd, isNew, localReg] = acquireLocalForwarder (ftn, initial);
1134+ if (!localFwd) {
1135+ // Setup for this track is in flight on this thread; drop the fanout.
1136+ co_return ;
1137+ }
11401138
11411139 auto p = startPublish (subscriberSession, localFwd, forward, pinned, nullptr );
11421140 if (!p) {
@@ -1208,8 +1206,12 @@ MoqxRelay::acquireLocalForwarder(const FullTrackName& ftn, const InitialTrackSta
12081206 return {std::move (localFwd), /* isNew=*/ true , localReg};
12091207 }
12101208 auto * ready = std::get_if<LocalForwarderRegistry::Ready>(&joined);
1211- XCHECK (ready) << " local forwarder entry still pending; a caller failed to resolve its claim: "
1212- << ftn;
1209+ if (!ready) {
1210+ // A setup on this thread owns the entry and cannot be joined mid-flight. Drop the
1211+ // fanout rather than publishing over a subscribe that is still claiming the track.
1212+ XLOG (WARNING ) << " local forwarder setup in flight, dropping publish fanout for " << ftn;
1213+ return {};
1214+ }
12131215 return {ready->forwarder , /* isNew=*/ false , localReg};
12141216}
12151217
@@ -1765,7 +1767,7 @@ std::optional<SubscribeError> MoqxRelay::completeUpstreamSubscription(
17651767// Runs on relayExec_. Registers the subscribe and wires the local forwarder to the
17661768// publisher; if it's a new subscription,also installs the passive relay chain, issues the
17671769// upstream subscribe, and completes the setup. The subscriberExec tail reads the
1768- // returned PublisherAttachment (handles upstreamOk) .
1770+ // returned PublisherAttachment.
17691771folly::coro::Task<MoqxRelay::PublisherAttachment> MoqxRelay::attachNewLocalForwarderOnRelayExec (
17701772 const SubscribeRequest& subReq,
17711773 LocalForwarderRegistry* localReg,
@@ -1809,6 +1811,12 @@ folly::coro::Task<MoqxRelay::PublisherAttachment> MoqxRelay::attachNewLocalForwa
18091811 co_await folly::coro::co_withExecutor (
18101812 folly::getKeepAliveToken (attach.publisherExec ),
18111813 [&]() -> folly::coro::Task<void > {
1814+ LocalForwarderRegistry::Claim publisherClaim;
1815+ if (sr.firstSetup ) {
1816+ publisherClaim =
1817+ installPublisherForwarder (ftn, attach.publisherFwd , /* removeOnEmpty=*/ true );
1818+ }
1819+
18121820 installChannelSubscriber (
18131821 *cbs.channelCb ,
18141822 *attach.publisherFwd ,
@@ -1837,7 +1845,14 @@ folly::coro::Task<MoqxRelay::PublisherAttachment> MoqxRelay::attachNewLocalForwa
18371845 attach.publisherFwd ,
18381846 setup.clientRequestID
18391847 );
1848+ if (upstreamResult->hasValue ()) {
1849+ const auto & ok = upstreamResult->value ();
1850+ publisherClaim.markReady (InitialTrackState{ok.largest , ok.extensions });
1851+ }
18401852 }
1853+ // Capture largest/extensions on publisherExec to set the initial state for the
1854+ // subscriber. Captured but ignored when the firstSetup returned an error.
1855+ attach.initial = InitialTrackState::capture (*attach.publisherFwd );
18411856 }()
18421857 );
18431858 // Back on relayExec_.
@@ -1882,7 +1897,6 @@ folly::coro::Task<MoqxRelay::PublisherAttachment> MoqxRelay::attachNewLocalForwa
18821897 attach.error = folly::makeUnexpected (std::move (*err));
18831898 co_return attach;
18841899 }
1885- attach.upstreamOk = std::move (upstreamOk);
18861900 co_return attach;
18871901}
18881902
@@ -1900,7 +1914,9 @@ MoqxRelay::joinOrPrepareUpstreamSubscription(SubscribeRequest subReq) {
19001914
19011915 auto firstOrSubsequent = registry_.getOrCreateFromSubscribe (
19021916 ftn,
1903- shared_from_this (),
1917+ // installPublisherForwarder puts the real chain on the forwarder's own exec instead;
1918+ // a relay-direct callback would run there and touch registry_ off relayExec_.
1919+ std::shared_ptr<MoQForwarder::Callback>(nullptr ),
19041920 [this , &ftn](std::shared_ptr<MoQForwarder> f) { return buildFilterChain (ftn, std::move (f)); }
19051921 );
19061922
@@ -2055,15 +2071,9 @@ folly::coro::Task<Publisher::SubscribeResult> MoqxRelay::subscribeFromSubscriber
20552071 co_return std::move (*attach.error );
20562072 }
20572073
2058- // Only the first subscriber has an upstream OK. A subsequent one leaves this empty and
2059- // marks the entry ready with no largest (a bug fixed in a subsequent commit)
2060- InitialTrackState initial;
2061- if (attach.upstreamOk ) {
2062- initial = InitialTrackState{attach.upstreamOk ->largest , attach.upstreamOk ->extensions };
2063- // Also on the subscriber, so a post-SUBSCRIBE_OK joining fetch resolves.
2064- initial.applyTo (*sub);
2065- }
2066- claim.setInitialState (initial);
2074+ claim.setInitialState (attach.initial );
2075+ // Also on the subscriber, so a post-SUBSCRIBE_OK joining fetch resolves.
2076+ attach.initial .applyTo (*sub);
20672077 replayPendingFowarderEvents (localFwd.get (), attach.finalCallback , *pendingCb, forward);
20682078 localFwd->tryProcessNewGroupRequest (subReq.params );
20692079 claim.markReady ();
0 commit comments