Skip to content

Commit 0231329

Browse files
afrindclaude
andcommitted
relay: resolve a pipelined joining FETCH against its own subscription
In local-forwarder mode, a joining FETCH pipelined behind its SUBSCRIBE could arrive before the relay finished setting up that subscription. The relay sent such a fetch upstream unresolved, where the publisher resolved it against the relay's own upstream subscription and its old or missing Largest. The fetch now waits for the setup and resolves against the joiner's Largest. A joining fetch that does not resolve fails at the relay. The wait adds an upstream RTT on a cold track and lets the cache serve the fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e49af0a commit 0231329

3 files changed

Lines changed: 355 additions & 21 deletions

File tree

‎src/MoqxRelay.cpp‎

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,43 @@ class MoqxRelay::LocalSubscribeFilter final : public PublisherCrossExecFilter {
224224
folly::coro::Task<FetchResult>
225225
fetch(moxygen::Fetch fetch, std::shared_ptr<moxygen::FetchConsumer> consumer) override {
226226
auto session = moxygen::MoQSession::getRequestSession();
227-
auto resolved = relay_->fetchOnSubscriberExec(std::move(fetch), session);
228-
// Joining resolved/deferred on subscriberExec; base filter wraps + hops to relayExec_.
229-
return PublisherCrossExecFilter::fetch(std::move(resolved), std::move(consumer));
227+
auto setup = relay_->resolveJoiningFetchOnSubscriberExec(fetch, *session);
228+
if (setup.hasError()) {
229+
return folly::coro::makeTask<FetchResult>(folly::makeUnexpected(std::move(setup.error())));
230+
}
231+
if (!setup->isReady()) {
232+
return fetchAfterSetup(
233+
std::move(*setup),
234+
std::move(fetch),
235+
std::move(session),
236+
std::move(consumer)
237+
);
238+
}
239+
// Base filter wraps + hops to relayExec_.
240+
return PublisherCrossExecFilter::fetch(std::move(fetch), std::move(consumer));
230241
}
231242

232243
private:
244+
folly::coro::Task<FetchResult> fetchAfterSetup(
245+
folly::SemiFuture<folly::Unit> setup,
246+
moxygen::Fetch fetch,
247+
std::shared_ptr<moxygen::MoQSession> session,
248+
std::shared_ptr<moxygen::FetchConsumer> consumer
249+
) {
250+
// A joining fetch pipelined behind its SUBSCRIBE resolves against the Largest from that
251+
// setup. The setup can finish before this task runs. Another SUBSCRIBE's setup may replace a
252+
// failed one.
253+
do {
254+
co_await folly::coro::co_awaitTry(std::move(setup));
255+
auto next = relay_->resolveJoiningFetchOnSubscriberExec(fetch, *session);
256+
if (next.hasError()) {
257+
co_return folly::makeUnexpected(std::move(next.error()));
258+
}
259+
setup = std::move(*next);
260+
} while (!setup.isReady());
261+
co_return co_await PublisherCrossExecFilter::fetch(std::move(fetch), std::move(consumer));
262+
}
263+
233264
std::shared_ptr<MoqxRelay> relay_;
234265
};
235266

@@ -2541,24 +2572,32 @@ MoqxRelay::trackStatusOnSubscriberExec(const TrackStatus& req) {
25412572
return Publisher::TrackStatusResult(buildTrackStatusOk(*localFwd, /*hasHandle=*/true, req));
25422573
}
25432574

2544-
Fetch MoqxRelay::fetchOnSubscriberExec(Fetch fetch, const std::shared_ptr<MoQSession>& session) {
2575+
folly::Expected<folly::SemiFuture<folly::Unit>, FetchError>
2576+
MoqxRelay::resolveJoiningFetchOnSubscriberExec(Fetch& fetch, const MoQSession& session) {
25452577
auto [standalone, joining] = fetchType(fetch);
25462578
if (!joining) {
2547-
return fetch;
2579+
return folly::makeSemiFuture();
25482580
}
2581+
// All of a session's subscriptions are on its thread's forwarders.
25492582
auto* localReg = tlForwarders_.get();
2550-
auto localFwd = localReg ? localReg->getIfReady(fetch.fullTrackName) : nullptr;
2551-
if (localFwd) {
2552-
auto res = localFwd->resolveJoiningFetch(session->sessionId(), *joining);
2553-
if (res.hasValue()) {
2554-
fetch.args = StandaloneFetch(res.value().start, res.value().end);
2555-
return fetch;
2556-
}
2583+
auto state = localReg ? localReg->lookup(fetch.fullTrackName) : LocalForwarderRegistry::State{};
2584+
if (auto* pending = std::get_if<LocalForwarderRegistry::Pending>(&state)) {
2585+
return std::move(pending->ready);
2586+
}
2587+
auto* ready = std::get_if<LocalForwarderRegistry::Ready>(&state);
2588+
if (!ready) {
2589+
return folly::makeUnexpected(FetchError{
2590+
fetch.requestID,
2591+
FetchErrorCode::DOES_NOT_EXIST,
2592+
"No subscription for joining fetch"
2593+
});
2594+
}
2595+
auto res = ready->forwarder->resolveJoiningFetch(session.sessionId(), *joining);
2596+
if (res.hasError()) {
2597+
return folly::makeUnexpected(std::move(res.error()));
25572598
}
2558-
// Not resolvable yet (no largest, or pre-PUBLISH_OK on draft-18's separate
2559-
// stream): defer upstream by track name. Rare; fails if there's no upstream.
2560-
joining->joiningRequestID = std::nullopt;
2561-
return fetch;
2599+
fetch.args = StandaloneFetch(res.value().start, res.value().end);
2600+
return folly::makeSemiFuture();
25622601
}
25632602

25642603
folly::coro::Task<Publisher::FetchResult>
@@ -2572,7 +2611,7 @@ MoqxRelay::fetchImpl(Fetch fetch, std::shared_ptr<FetchConsumer> consumer) {
25722611
}
25732612

25742613
auto [standalone, joining] = fetchType(fetch);
2575-
// LF mode resolves/defers joining in fetchOnSubscriberExec on the subscriber exec.
2614+
// LF mode resolves joining fetches on the subscriber exec.
25762615
if (joining && mode() != Mode::LocalForwarder) {
25772616
auto fetchView = registry_.getFetchView(fetch.fullTrackName);
25782617
if (!fetchView) {

‎src/MoqxRelay.h‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,10 +580,10 @@ class MoqxRelay : public moxygen::Publisher,
580580
std::optional<moxygen::Publisher::TrackStatusResult>
581581
trackStatusOnSubscriberExec(const moxygen::TrackStatus& req);
582582

583-
// Resolves a joining fetch against this thread's local forwarder (race-free). Rewrites to a
584-
// standalone Fetch when largest is known, else clears joiningRequestID to defer to upstream.
585-
moxygen::Fetch
586-
fetchOnSubscriberExec(moxygen::Fetch fetch, const std::shared_ptr<moxygen::MoQSession>& session);
583+
// Rewrites a joining fetch to a standalone fetch against this thread's local forwarder. Returns
584+
// the setup to wait for while that forwarder is pending, and a ready future otherwise.
585+
folly::Expected<folly::SemiFuture<folly::Unit>, moxygen::FetchError>
586+
resolveJoiningFetchOnSubscriberExec(moxygen::Fetch& fetch, const moxygen::MoQSession& session);
587587

588588
// Impl methods — run on relayExec_ when set, or inline when relayExec_==nullptr.
589589
folly::coro::Task<SubscribeResult>

0 commit comments

Comments
 (0)