Skip to content

Commit 0b901a8

Browse files
akash-a-nmeta-codesync[bot]
authored andcommitted
Implement dynamic groups and new group request in relay (#112)
Summary: Add support for DYNAMIC_GROUPS track extension and NEW_GROUP_REQUEST (NGR) parameter forwarding through the relay. - MoQForwarder tracks outstanding NGR and clears it when upstream largest group advances; exposes tryProcessNewGroupRequest() to consolidate the extract/gate/set/fire pattern used at all call sites - MoQRelay forwards NGR upstream via REQUEST_UPDATE when a downstream subscriber requests a new group on an existing upstream subscription; passes NGR through on new upstream subscribes without an extra update - Track status returns local forwarder state when an active subscription exists, falling back to upstream only when no subscription is found Co-authored by: akash-a-n Pull Request resolved: #112 Reviewed By: sandarsh Differential Revision: D95894248 Pulled By: afrind fbshipit-source-id: cdda93666230a7931e41f058672e2e62bb530da7
1 parent 1282102 commit 0b901a8

9 files changed

Lines changed: 609 additions & 33 deletions

File tree

moxygen/MoQFramer.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ bool isRequestSpecificParam(moxygen::TrackRequestParamKey key) {
4949
case moxygen::TrackRequestParamKey::GROUP_ORDER:
5050
case moxygen::TrackRequestParamKey::SUBSCRIBER_PRIORITY:
5151
case moxygen::TrackRequestParamKey::FORWARD:
52+
case moxygen::TrackRequestParamKey::NEW_GROUP_REQUEST:
5253
return true;
5354
default:
5455
return false;
@@ -4458,6 +4459,16 @@ WriteResult MoQFrameWriter::writeSubscribeRequestHelper(
44584459
forwardParam.asUint64 = 0;
44594460
requestSpecificParams.push_back(forwardParam);
44604461
}
4462+
4463+
auto newGroupRequestValue = getFirstIntParam(
4464+
subscribeRequest.params, TrackRequestParamKey::NEW_GROUP_REQUEST);
4465+
if (newGroupRequestValue.has_value()) {
4466+
Parameter newGroupRequestParam;
4467+
newGroupRequestParam.key =
4468+
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST);
4469+
newGroupRequestParam.asUint64 = *newGroupRequestValue;
4470+
requestSpecificParams.push_back(newGroupRequestParam);
4471+
}
44614472
} else {
44624473
writeVarint(
44634474
writeBuf,
@@ -4544,6 +4555,16 @@ WriteResult MoQFrameWriter::writeRequestUpdate(
45444555
forwardParam.asUint64 = *update.forward ? 1 : 0;
45454556
requestSpecificParams.push_back(forwardParam);
45464557
}
4558+
4559+
auto newGroupRequestValue = getFirstIntParam(
4560+
update.params, TrackRequestParamKey::NEW_GROUP_REQUEST);
4561+
if (newGroupRequestValue.has_value()) {
4562+
Parameter newGroupRequestParam;
4563+
newGroupRequestParam.key =
4564+
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST);
4565+
newGroupRequestParam.asUint64 = *newGroupRequestValue;
4566+
requestSpecificParams.push_back(newGroupRequestParam);
4567+
}
45474568
} else {
45484569
// For draft < 15, start and endGroup are mandatory
45494570
XCHECK(update.start.has_value()) << "start is required for draft < 15";
@@ -4879,6 +4900,16 @@ WriteResult MoQFrameWriter::writePublishOk(
48794900
forwardParam.asUint64 = 0;
48804901
requestSpecificParams.push_back(forwardParam);
48814902
}
4903+
4904+
auto newGroupRequestValue = getFirstIntParam(
4905+
publishOk.params, TrackRequestParamKey::NEW_GROUP_REQUEST);
4906+
if (newGroupRequestValue.has_value()) {
4907+
Parameter newGroupRequestParam;
4908+
newGroupRequestParam.key =
4909+
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST);
4910+
newGroupRequestParam.asUint64 = *newGroupRequestValue;
4911+
requestSpecificParams.push_back(newGroupRequestParam);
4912+
}
48824913
} else {
48834914
writeVarint(
48844915
writeBuf,

moxygen/MoQTypes.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ const folly::F14FastSet<FrameType> kAllowedFramesForForward = {
237237
FrameType::PUBLISH_OK,
238238
FrameType::SUBSCRIBE_NAMESPACE};
239239

240+
const folly::F14FastSet<FrameType> kAllowedFramesForNewGroupRequest = {
241+
FrameType::SUBSCRIBE,
242+
FrameType::REQUEST_UPDATE,
243+
FrameType::PUBLISH_OK};
244+
240245
// Allowlist mapping: TrackRequestParamKey -> set of allowed FrameTypes
241246
// Empty set means allowed for all frame types
242247
const folly::F14FastMap<TrackRequestParamKey, folly::F14FastSet<FrameType>>
@@ -254,6 +259,8 @@ const folly::F14FastMap<TrackRequestParamKey, folly::F14FastSet<FrameType>>
254259
{TrackRequestParamKey::GROUP_ORDER, kAllowedFramesForGroupOrder},
255260
{TrackRequestParamKey::LARGEST_OBJECT, kAllowedFramesForLargestObject},
256261
{TrackRequestParamKey::FORWARD, kAllowedFramesForForward},
262+
{TrackRequestParamKey::NEW_GROUP_REQUEST,
263+
kAllowedFramesForNewGroupRequest},
257264
};
258265

259266
// Frame types that allow all parameters (no validation)

moxygen/MoQTypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ enum class TrackRequestParamKey : uint64_t {
445445
GROUP_ORDER = 0x22,
446446
LARGEST_OBJECT = 0x9,
447447
FORWARD = 0x10,
448+
NEW_GROUP_REQUEST = 0x32,
448449
};
449450

450451
class Parameters {

moxygen/relay/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ moxygen_add_library(moxygen_relay_moq_cache
2727
Folly::folly_container_f14_hash
2828
Folly::folly_coro_baton
2929
Folly::folly_coro_task
30+
Folly::folly_logging_logging
3031
)
3132

3233
moxygen_add_library(moxygen_relay_moq_relay
@@ -35,6 +36,7 @@ moxygen_add_library(moxygen_relay_moq_relay
3536
EXPORTED_DEPS
3637
moxygen_moq
3738
moxygen_moq_consumers
39+
moxygen_moq_types
3840
moxygen_relay_moq_cache
3941
moxygen_relay_moq_forwarder
4042
Folly::folly_container_f14_hash

moxygen/relay/MoQForwarder.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,11 @@ void MoQForwarder::removeSubscriberIt(
361361
void MoQForwarder::updateLargest(uint64_t group, uint64_t object) {
362362
AbsoluteLocation now{group, object};
363363
if (!largest_ || now > *largest_) {
364+
// Clear any outstanding NEW_GROUP_REQUEST once the upstream group advances.
365+
if (outstandingNewGroupRequest_.has_value() &&
366+
(!largest_ || group > largest_->group)) {
367+
outstandingNewGroupRequest_.reset();
368+
}
364369
largest_ = now;
365370
}
366371
}
@@ -529,6 +534,34 @@ void MoQForwarder::removeForwardingSubscriber() {
529534
}
530535
}
531536

537+
void MoQForwarder::tryProcessNewGroupRequest(
538+
const Parameters& params,
539+
bool fire) {
540+
auto value =
541+
getFirstIntParam(params, TrackRequestParamKey::NEW_GROUP_REQUEST);
542+
if (!value.has_value()) {
543+
return;
544+
}
545+
// the track must support dynamic groups.
546+
if (!getPublisherDynamicGroups(extensions_).value_or(false)) {
547+
return;
548+
}
549+
// non-zero value <= LargestGroup means the new group is already
550+
// available — do not send upstream.
551+
if (*value != 0 && largest_.has_value() && *value <= largest_->group) {
552+
return;
553+
}
554+
// already have an outstanding request with >= value.
555+
if (outstandingNewGroupRequest_.has_value() &&
556+
*outstandingNewGroupRequest_ >= *value) {
557+
return;
558+
}
559+
outstandingNewGroupRequest_ = *value;
560+
if (fire && callback_) {
561+
callback_->newGroupRequested(this, *value);
562+
}
563+
}
564+
532565
Payload MoQForwarder::maybeClone(const Payload& payload) {
533566
return payload ? payload->clone() : nullptr;
534567
}
@@ -597,13 +630,30 @@ void MoQForwarder::Subscriber::setParam(const TrackRequestParameter& param) {
597630
}
598631
}
599632

633+
void MoQForwarder::Subscriber::onPublishOk(const PublishOk& pubOk) {
634+
// Update subscriber range from PUBLISH_OK
635+
std::optional<AbsoluteLocation> end;
636+
if (pubOk.endGroup) {
637+
end = AbsoluteLocation{*pubOk.endGroup, 0};
638+
}
639+
range =
640+
toSubscribeRange(pubOk.start, end, pubOk.locType, forwarder.largest());
641+
642+
// Update forward flag
643+
shouldForward = pubOk.forward;
644+
645+
// Handle NEW_GROUP_REQUEST forwarding if present
646+
forwarder.tryProcessNewGroupRequest(pubOk.params);
647+
}
648+
600649
folly::coro::Task<folly::Expected<RequestOk, RequestError>>
601650
MoQForwarder::Subscriber::requestUpdate(RequestUpdate requestUpdate) {
602651
// Validation:
603652
// - Start location can be updated
604653
// - End location can increase or decrease
605654
// - For bounded subscriptions (endGroup > 0), end must be >= start
606655
// - Forward state is optional and only updated if explicitly provided
656+
// - A New Group can be requested
607657

608658
// Only update start if provided
609659
if (requestUpdate.start.has_value()) {
@@ -639,6 +689,9 @@ MoQForwarder::Subscriber::requestUpdate(RequestUpdate requestUpdate) {
639689
forwarder.removeForwardingSubscriber();
640690
}
641691

692+
// Only update new group request if provided
693+
forwarder.tryProcessNewGroupRequest(requestUpdate.params);
694+
642695
co_return RequestOk{.requestID = requestUpdate.requestID};
643696
}
644697

moxygen/relay/MoQForwarder.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ class MoQForwarder : public TrackConsumer {
4949
return extensions_;
5050
}
5151

52+
// Extract the NEW_GROUP_REQUEST param from `params`, check if it should
53+
// be forwarded upstream, record it as outstanding, and optionally fire
54+
// the newGroupRequested callback. Pass fire=false when the NGR already
55+
// rides an outgoing SUBSCRIBE and no extra REQUEST_UPDATE is needed.
56+
void tryProcessNewGroupRequest(const Parameters& params, bool fire = true);
57+
5258
void setLargest(AbsoluteLocation largest);
5359

5460
std::optional<AbsoluteLocation> largest() {
@@ -60,6 +66,8 @@ class MoQForwarder : public TrackConsumer {
6066
virtual ~Callback() = default;
6167
virtual void onEmpty(MoQForwarder*) = 0;
6268
virtual void forwardChanged(MoQForwarder*) {}
69+
// This fires whenever an unseen NGR is received
70+
virtual void newGroupRequested(MoQForwarder*, uint64_t /*group*/) {}
6371
};
6472

6573
void setCallback(std::shared_ptr<Callback> callback);
@@ -107,6 +115,10 @@ class MoQForwarder : public TrackConsumer {
107115
// requestID and trackAlias are placeholders (overwritten by MoQSession).
108116
PublishRequest getPublishRequest() const;
109117

118+
// Process PUBLISH_OK response, updating range, forward flag, and handling
119+
// NEW_GROUP_REQUEST forwarding via callback
120+
void onPublishOk(const PublishOk& pubOk);
121+
110122
folly::coro::Task<folly::Expected<RequestOk, RequestError>> requestUpdate(
111123
RequestUpdate requestUpdate) override;
112124

@@ -317,6 +329,9 @@ class MoQForwarder : public TrackConsumer {
317329
GroupOrder groupOrder_{GroupOrder::OldestFirst};
318330
std::optional<AbsoluteLocation> largest_;
319331
Extensions extensions_;
332+
// The NEW_GROUP_REQUEST value most recently forwarded upstream; cleared when
333+
// the upstream Largest Group advances (indicating the request was fulfilled).
334+
std::optional<uint64_t> outstandingNewGroupRequest_{};
320335
std::shared_ptr<Callback> callback_;
321336
uint64_t forwardingSubscribers_{0};
322337
bool draining_{false};

moxygen/relay/MoQRelay.cpp

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "moxygen/relay/MoQRelay.h"
88
#include "moxygen/MoQFilters.h"
9+
#include "moxygen/MoQTrackProperties.h"
910

1011
namespace {
1112
constexpr uint8_t kDefaultUpstreamPriority = 128;
@@ -34,6 +35,24 @@ folly::coro::Task<void> MoQRelay::doSubscribeUpdate(
3435
}
3536
}
3637

38+
// Sends a REQUEST_UPDATE that carries only the NEW_GROUP_REQUEST parameter.
39+
folly::coro::Task<void> MoQRelay::doNewGroupRequestUpdate(
40+
std::shared_ptr<Publisher::SubscriptionHandle> handle,
41+
uint64_t newGroupRequestValue) {
42+
XLOG(DBG4) << "Sending NEW_GROUP_REQUEST update: " << newGroupRequestValue;
43+
RequestUpdate update;
44+
update.requestID = RequestID(0);
45+
update.existingRequestID = handle->subscribeOk().requestID;
46+
update.params.insertParam(Parameter(
47+
folly::to_underlying(TrackRequestParamKey::NEW_GROUP_REQUEST),
48+
newGroupRequestValue));
49+
auto updateRes = co_await handle->requestUpdate(std::move(update));
50+
if (updateRes.hasError()) {
51+
XLOG(ERR) << "NEW_GROUP_REQUEST update failed: "
52+
<< updateRes.error().reasonPhrase;
53+
}
54+
}
55+
3756
std::shared_ptr<MoQRelay::NamespaceNode> MoQRelay::findNamespaceNode(
3857
const TrackNamespace& ns,
3958
bool createMissingNodes,
@@ -509,13 +528,10 @@ folly::coro::Task<void> MoQRelay::publishToSession(
509528
guard.dismiss();
510529
XLOG(DBG1) << "Publish OK sess=" << session.get();
511530
auto& pubOk = pubResult.value().value();
512-
std::optional<AbsoluteLocation> end;
513-
if (pubOk.endGroup) {
514-
end = AbsoluteLocation{*pubOk.endGroup, 0};
515-
}
516-
subscriber->range =
517-
toSubscribeRange(pubOk.start, end, pubOk.locType, forwarder->largest());
518-
subscriber->shouldForward = pubOk.forward;
531+
532+
// Process the PUBLISH_OK response - updates range, forward flag, and
533+
// handles NEW_GROUP_REQUEST forwarding via callback
534+
subscriber->onPublishOk(pubOk);
519535
}
520536

521537
class MoQRelay::NamespaceSubscription
@@ -871,6 +887,8 @@ folly::coro::Task<Publisher::SubscribeResult> MoQRelay::subscribe(
871887
auto& rsub = it->second;
872888
rsub.requestID = subRes.value()->subscribeOk().requestID;
873889
rsub.handle = std::move(subRes.value());
890+
// Record NGR as outstanding (no fire — it rides the outgoing SUBSCRIBE).
891+
forwarder->tryProcessNewGroupRequest(subReq.params, /*fire=*/false);
874892
rsub.promise.setValue(folly::unit);
875893
co_return subscriber;
876894
} else {
@@ -911,6 +929,8 @@ folly::coro::Task<Publisher::SubscribeResult> MoQRelay::subscribe(
911929
doSubscribeUpdate(subscriptionIt->second.handle, /*forward=*/true))
912930
.start();
913931
}
932+
933+
forwarder->tryProcessNewGroupRequest(subReq.params);
914934
co_return subscriber;
915935
}
916936
}
@@ -1110,4 +1130,24 @@ void MoQRelay::forwardChanged(MoQForwarder* forwarder) {
11101130
.start();
11111131
}
11121132

1133+
void MoQRelay::newGroupRequested(MoQForwarder* forwarder, uint64_t group) {
1134+
auto subscriptionIt = subscriptions_.find(forwarder->fullTrackName());
1135+
if (subscriptionIt == subscriptions_.end()) {
1136+
return;
1137+
}
1138+
auto& subscription = subscriptionIt->second;
1139+
// Check if handle is still valid (publisher may have terminated)
1140+
if (!subscription.handle) {
1141+
XLOG(DBG4) << "Ignoring NEW_GROUP_REQUEST for " << subscriptionIt->first
1142+
<< " - publisher terminated";
1143+
return;
1144+
}
1145+
XLOG(INFO) << "New group request detected for " << subscriptionIt->first;
1146+
1147+
auto exec = subscription.upstream->getExecutor();
1148+
auto handle = subscription.handle;
1149+
co_withExecutor(exec, doNewGroupRequestUpdate(std::move(handle), group))
1150+
.start();
1151+
}
1152+
11131153
} // namespace moxygen

moxygen/relay/MoQRelay.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ class MoQRelay : public Publisher,
188188

189189
void onEmpty(MoQForwarder* forwarder) override;
190190
void forwardChanged(MoQForwarder* forwarder) override;
191+
void newGroupRequested(MoQForwarder* forwarder, uint64_t group) override;
191192

192193
folly::coro::Task<void> publishNamespaceToSession(
193194
std::shared_ptr<MoQSession> session,
@@ -203,6 +204,10 @@ class MoQRelay : public Publisher,
203204
std::shared_ptr<Publisher::SubscriptionHandle> handle,
204205
bool forward);
205206

207+
folly::coro::Task<void> doNewGroupRequestUpdate(
208+
std::shared_ptr<Publisher::SubscriptionHandle> handle,
209+
uint64_t newGroupRequestValue);
210+
206211
void publishNamespaceDone(
207212
const TrackNamespace& trackNamespace,
208213
NamespaceNode* node);

0 commit comments

Comments
 (0)