Skip to content

Commit 8a963bc

Browse files
authored
Merge pull request #196 from englishm-cloudflare/me/release-idle-upstream-subscriptions
2 parents 2b9a51d + 600e2c5 commit 8a963bc

17 files changed

Lines changed: 1740 additions & 150 deletions

File tree

moq-relay-ietf/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ thiserror = "2.0.17"
7070
metrics = "0.24"
7171
metrics-exporter-prometheus = { version = "0.16", optional = true }
7272

73+
[dev-dependencies]
74+
# test-util provides the paused-clock runtime used to test idle timeouts without
75+
# sleeping for real.
76+
tokio = { version = "1", features = ["full", "test-util"] }
77+
7378
[features]
7479
default = []
7580
metrics-prometheus = ["dep:metrics-exporter-prometheus"]

moq-relay-ietf/src/bin/moq-relay-ietf/main.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use url::Url;
1313
use api_coordinator::{ApiCoordinator, ApiCoordinatorConfig};
1414
use file_coordinator::FileCoordinator;
1515
use moq_relay_ietf::{Coordinator, Relay, RelayConfig, SessionConfig, Web, WebConfig};
16+
use std::time::Duration;
1617

1718
#[derive(Parser, Clone)]
1819
pub struct Cli {
@@ -36,6 +37,12 @@ pub struct Cli {
3637
#[arg(long, default_value_t = 100)]
3738
pub max_request_id: u64,
3839

40+
/// Seconds to keep a cached track with no subscribers before releasing its
41+
/// upstream subscription. 0 disables eviction, holding upstream
42+
/// subscriptions for the lifetime of the upstream session.
43+
#[arg(long, default_value_t = 30)]
44+
pub cache_idle_timeout: u64,
45+
3946
/// Forward all PUBLISH_NAMESPACE messages to the provided server for auth/routing.
4047
/// If not provided, the relay accepts every unique namespace publish.
4148
#[arg(long)]
@@ -188,23 +195,26 @@ async fn main() -> anyhow::Result<()> {
188195
};
189196

190197
// Create a QUIC server for media.
191-
let relay = Relay::new(RelayConfig {
192-
tls: tls.clone(),
193-
bind: Some(cli.bind),
194-
endpoints: vec![],
195-
qlog_dir: qlog_dir_for_relay,
196-
mlog_dir: mlog_dir_for_relay,
197-
node: cli.node,
198-
announce: cli.announce,
199-
coordinator,
200-
session: SessionConfig {
201-
max_request_id: cli.max_request_id,
198+
let relay = Relay::new_with_cache_idle_timeout(
199+
RelayConfig {
200+
tls: tls.clone(),
201+
bind: Some(cli.bind),
202+
endpoints: vec![],
203+
qlog_dir: qlog_dir_for_relay,
204+
mlog_dir: mlog_dir_for_relay,
205+
node: cli.node,
206+
announce: cli.announce,
207+
coordinator,
208+
session: SessionConfig {
209+
max_request_id: cli.max_request_id,
210+
},
211+
// No connection tagger: the default binary treats every inbound
212+
// connection as a public client. Embedders that run relay-to-relay
213+
// meshes supply a tagger to mark internal peers.
214+
connection_tagger: None,
202215
},
203-
// No connection tagger: the default binary treats every inbound
204-
// connection as a public client. Embedders that run relay-to-relay
205-
// meshes supply a tagger to mark internal peers.
206-
connection_tagger: None,
207-
})?;
216+
Duration::from_secs(cli.cache_idle_timeout),
217+
)?;
208218

209219
if cli.dev {
210220
// Create a web server too.

moq-relay-ietf/src/consumer.rs

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use moq_transport::{
1212
};
1313
use tokio::sync::Semaphore;
1414

15-
use crate::{metrics::GaugeGuard, Coordinator, Locals, Producer, RemoteManager, SessionContext};
15+
use crate::{
16+
metrics::GaugeGuard, Coordinator, Locals, Producer, RemoteManager, SessionContext, TrackRequest,
17+
};
1618

1719
const MAX_INBOUND_PUBLISH_TRACKS_PER_SESSION: usize = 1024;
1820

@@ -254,11 +256,11 @@ impl Consumer {
254256
tracing::info!(namespace = %ns, "PUBLISH_NAMESPACE closed");
255257
return Ok(());
256258
},
257-
Some(track) = requests.recv() => {
259+
Some(TrackRequest { writer, lease }) = requests.recv() => {
258260
let mut subscriber = self.subscriber.clone();
259261

260262
tasks.push(async move {
261-
let info = track.clone();
263+
let info = writer.info.clone();
262264
let namespace = info.namespace.to_utf8_path();
263265
let track_name = info.name.clone();
264266
tracing::info!(
@@ -267,15 +269,47 @@ impl Consumer {
267269
"forwarding subscribe: {:?}", info
268270
);
269271

270-
if let Err(err) = subscriber.subscribe(track).await {
271-
tracing::warn!(
272-
namespace = %namespace,
273-
track = %track_name,
274-
error = %err,
275-
"failed forwarding subscribe: {:?}", info
276-
)
272+
// Hold the subscription explicitly rather than using
273+
// `subscribe()`, so it can be dropped — sending
274+
// UNSUBSCRIBE — once downstream interest goes away.
275+
let subscribe = match subscriber.subscribe_open(writer).await {
276+
Ok(subscribe) => subscribe,
277+
Err(err) => {
278+
tracing::warn!(
279+
namespace = %namespace,
280+
track = %track_name,
281+
error = %err,
282+
"failed forwarding subscribe: {:?}", info
283+
);
284+
return Ok(());
285+
}
286+
};
287+
288+
tokio::select! {
289+
res = subscribe.closed() => {
290+
if let Err(err) = res {
291+
tracing::warn!(
292+
namespace = %namespace,
293+
track = %track_name,
294+
error = %err,
295+
"failed forwarding subscribe: {:?}", info
296+
)
297+
}
298+
}
299+
// The cached track went unwatched long enough to be
300+
// evicted, so stop pulling it. Dropping `subscribe`
301+
// below sends UNSUBSCRIBE upstream.
302+
_ = lease.released() => {
303+
tracing::info!(
304+
namespace = %namespace,
305+
track = %track_name,
306+
"releasing upstream subscription for idle cached track"
307+
);
308+
}
277309
}
278310

311+
drop(subscribe);
312+
279313
Ok(())
280314
}.boxed());
281315
},
@@ -352,13 +386,13 @@ impl Consumer {
352386
}
353387

354388
tracing::debug!(
355-
namespace = %namespace.to_utf8_path(),
389+
namespace = %namespace,
356390
track = %track_name,
357391
"PUBLISH registered as exact local track"
358392
);
359393

360394
publish.closed().await?;
361-
tracing::info!(namespace = %namespace.to_utf8_path(), track = %track_name, "PUBLISH closed");
395+
tracing::info!(namespace = %namespace, track = %track_name, "PUBLISH closed");
362396

363397
Ok(())
364398
}

0 commit comments

Comments
 (0)