Harden concurrency and resource lifecycle (#101)
Some checks failed
Native code generation / deterministic (push) Failing after 2m4s
Concurrency and resource soak audit / soak (push) Failing after 6m31s
Imaging and meshing gate / native (push) Failing after 2m52s
JPEG 2000 feature / linux (push) Successful in 2m45s
Release platform and feature matrix / audit (push) Successful in 35s
Native Rust workspace compile / compile (push) Failing after 54s
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
Some checks failed
Native code generation / deterministic (push) Failing after 2m4s
Concurrency and resource soak audit / soak (push) Failing after 6m31s
Imaging and meshing gate / native (push) Failing after 2m52s
JPEG 2000 feature / linux (push) Successful in 2m45s
Release platform and feature matrix / audit (push) Successful in 35s
Native Rust workspace compile / compile (push) Failing after 54s
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
This commit is contained in:
@@ -20,7 +20,7 @@ use std::io::Cursor;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use str0m::change::{SdpAnswer, SdpOffer};
|
||||
@@ -1426,6 +1426,7 @@ where
|
||||
/// Deterministic native WebRTC answerer used by CI and offline diagnostics.
|
||||
pub struct LoopbackSignaling {
|
||||
state: tokio::sync::Mutex<LoopbackState>,
|
||||
provisioning: AtomicBool,
|
||||
active_tasks: Arc<AtomicUsize>,
|
||||
received_messages: Arc<Mutex<Vec<String>>>,
|
||||
peer_id: UUID,
|
||||
@@ -1448,6 +1449,7 @@ impl LoopbackSignaling {
|
||||
completed: false,
|
||||
closed: false,
|
||||
}),
|
||||
provisioning: AtomicBool::new(false),
|
||||
active_tasks: Arc::new(AtomicUsize::new(0)),
|
||||
received_messages: Arc::new(Mutex::new(Vec::new())),
|
||||
peer_id,
|
||||
@@ -1481,10 +1483,21 @@ impl LoopbackSignaling {
|
||||
impl VoiceSignaling for LoopbackSignaling {
|
||||
fn provision(&self, request: ProvisionRequest) -> SignalFuture<'_, ProvisionResponse> {
|
||||
Box::pin(async move {
|
||||
let mut state = self.state.lock().await;
|
||||
if state.task.is_some() {
|
||||
struct ProvisionGuard<'a>(&'a AtomicBool);
|
||||
impl Drop for ProvisionGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
if self
|
||||
.provisioning
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
return Err(WebRtcError::Signaling("duplicate provision request".into()));
|
||||
}
|
||||
let _provisioning = ProvisionGuard(&self.provisioning);
|
||||
if request.jsep.kind != "offer"
|
||||
|| request.channel_type != "local"
|
||||
|| request.voice_server_type != "webrtc"
|
||||
@@ -1511,6 +1524,10 @@ impl VoiceSignaling for LoopbackSignaling {
|
||||
let viewer_session = UUID::random()
|
||||
.map_err(|_| WebRtcError::Signaling("could not create session id".into()))?;
|
||||
let credentials = VoiceSecret::new("offline-secret")?;
|
||||
let mut state = self.state.lock().await;
|
||||
if state.task.is_some() {
|
||||
return Err(WebRtcError::Signaling("duplicate provision request".into()));
|
||||
}
|
||||
active.fetch_add(1, Ordering::AcqRel);
|
||||
let task = tokio::spawn(async move {
|
||||
Box::pin(run_loopback_peer(
|
||||
@@ -1815,6 +1832,33 @@ mod tests {
|
||||
assert_eq!(signaling.active_tasks(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn concurrent_provisioning_admits_one_session_without_leaking_the_loser() {
|
||||
let peer = UUID::new_with_string("11111111-2222-4333-8444-555555555555".into()).unwrap();
|
||||
let signaling = LoopbackSignaling::new(peer);
|
||||
let config = VoiceSessionConfig {
|
||||
timeout: Duration::from_secs(10),
|
||||
..VoiceSessionConfig::default()
|
||||
};
|
||||
let (first, second) = tokio::join!(
|
||||
WebRtcVoiceSession::connect(signaling.clone(), config.clone()),
|
||||
WebRtcVoiceSession::connect(signaling.clone(), config),
|
||||
);
|
||||
let (mut session, rejected) = match (first, second) {
|
||||
(Ok(session), Err(error)) | (Err(error), Ok(session)) => (session, error),
|
||||
(Ok(_), Ok(_)) => panic!("duplicate provisioning created two sessions"),
|
||||
(Err(first), Err(second)) => {
|
||||
panic!("both provisioning attempts failed: {first}; {second}")
|
||||
}
|
||||
};
|
||||
assert!(matches!(rejected, WebRtcError::Signaling(_)));
|
||||
|
||||
session.shutdown().await.unwrap();
|
||||
signaling.wait_closed(Duration::from_secs(2)).await.unwrap();
|
||||
assert_eq!(session.snapshot().active_tasks, 0);
|
||||
assert_eq!(signaling.active_tasks(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn failed_answer_closes_provisioned_server_session() {
|
||||
let signaling = Arc::new(InvalidAnswerSignaling {
|
||||
|
||||
Reference in New Issue
Block a user