use crate::client::config::ClientConfig;
use crate::client::transports::{Client, ClientCore};
use crate::common::config_types::TransportProtocol;
use crate::common::error::{FlareError, Result};
use crate::common::platform::{MonotonicInstant, monotonic_now, timeout as platform_timeout};
use crate::common::protocol::Frame;
use crate::transport::events::ArcObserver;
use async_trait::async_trait;
use futures_util::future::select_all;
use std::sync::{Arc, Mutex as StdMutex};
use std::time::Duration;
use tokio::sync::Mutex;
#[cfg(feature = "quic")]
use crate::client::transports::quic::QUICClient;
#[cfg(feature = "tcp")]
use crate::client::transports::tcp::TCPClient;
#[cfg(feature = "websocket")]
use crate::client::transports::websocket::WebSocketClient;
pub struct HybridClient {
inner: Arc<Mutex<Box<dyn Client>>>,
active_protocol: TransportProtocol,
core: ClientCore,
}
type ConnectionResult = Result<Box<dyn Client>>;
type ConnectionTaskResult = (TransportProtocol, usize, ConnectionResult, Duration);
type RaceInflight = Arc<StdMutex<Vec<tokio::task::JoinHandle<ConnectionTaskResult>>>>;
type SuccessfulConnection = (usize, TransportProtocol, Box<dyn Client>, Duration);
type FailedConnection = (usize, TransportProtocol, FlareError, Duration);
impl HybridClient {
pub fn new(config: ClientConfig) -> Result<Self> {
let core = ClientCore::new(&config);
let protocols = config.get_protocols();
if protocols.len() == 1 {
Self::create_single_protocol(config, protocols[0], core)
} else {
Self::create_race_mode_placeholder(config, protocols[0], core)
}
}
fn create_single_protocol(
config: ClientConfig,
protocol: TransportProtocol,
core: ClientCore,
) -> Result<Self> {
let mut single_config = config;
single_config.transport = protocol;
single_config.transports = None;
single_config.server_url = single_config.get_protocol_url(&protocol);
let client = Self::create_protocol_client(single_config, core.clone())?;
Ok(Self {
inner: Arc::new(Mutex::new(client)),
active_protocol: protocol,
core,
})
}
fn create_race_mode_placeholder(
config: ClientConfig,
default_protocol: TransportProtocol,
core: ClientCore,
) -> Result<Self> {
let default_config = ClientConfig {
server_url: config.get_protocol_url(&default_protocol),
transport: default_protocol,
..config
};
let client = Self::create_protocol_client(default_config, core.clone())?;
Ok(Self {
inner: Arc::new(Mutex::new(client)),
active_protocol: default_protocol,
core,
})
}
fn create_protocol_client(config: ClientConfig, core: ClientCore) -> Result<Box<dyn Client>> {
match config.transport {
TransportProtocol::WebSocket => {
#[cfg(feature = "websocket")]
{
Ok(Box::new(WebSocketClient::with_core(config, core)))
}
#[cfg(not(feature = "websocket"))]
{
let _ = (config, core);
Err(Self::transport_feature_disabled(
TransportProtocol::WebSocket,
))
}
}
TransportProtocol::QUIC => {
#[cfg(feature = "quic")]
{
QUICClient::with_core(config, core).map(|c| Box::new(c) as Box<dyn Client>)
}
#[cfg(not(feature = "quic"))]
{
let _ = (config, core);
Err(Self::transport_feature_disabled(TransportProtocol::QUIC))
}
}
TransportProtocol::TCP => {
#[cfg(feature = "tcp")]
{
Ok(Box::new(TCPClient::with_core(config, core)))
}
#[cfg(not(feature = "tcp"))]
{
let _ = (config, core);
Err(Self::transport_feature_disabled(TransportProtocol::TCP))
}
}
}
}
#[allow(dead_code)]
fn transport_feature_disabled(protocol: TransportProtocol) -> FlareError {
FlareError::operation_not_supported(format!("{protocol:?} transport feature is disabled"))
}
async fn race_connect(
config: ClientConfig,
shared_core: ClientCore,
inflight: RaceInflight,
) -> Result<(Box<dyn Client>, TransportProtocol)> {
let protocols = config.get_protocols();
let race_start = monotonic_now();
Self::spawn_connection_tasks(config, &protocols, &shared_core, Arc::clone(&inflight));
let (first_success, successful_clients, errors) =
Self::wait_for_connections(inflight).await;
Self::process_race_results(
first_success,
successful_clients,
errors,
shared_core,
race_start,
)
.await
}
fn spawn_connection_tasks(
config: ClientConfig,
protocols: &[TransportProtocol],
shared_core: &ClientCore,
inflight: RaceInflight,
) {
for (index, protocol) in protocols.iter().enumerate() {
let protocol_url = config.get_protocol_url(protocol);
tracing::debug!(
"协议竞速: [{}] {:?} 使用地址: {}",
index,
protocol,
protocol_url
);
let protocol_config = ClientConfig {
server_url: protocol_url.clone(),
transport: *protocol,
transports: None,
..config.clone()
};
let mut protocol_core = ClientCore::new(&protocol_config);
protocol_core.share_race_state_from(shared_core);
let protocol_clone = *protocol;
let protocol_index = index;
let handle = tokio::spawn(async move {
let start_time = monotonic_now();
tracing::debug!(
"开始建立网络连接: {:?} (优先级: {}, 地址: {})",
protocol_clone,
protocol_index,
protocol_url
);
let network_result = Self::establish_protocol_network(
protocol_clone,
protocol_config,
protocol_core,
protocol_index,
)
.await;
let elapsed = start_time.elapsed();
let client_result = network_result.map(|(client, _)| client);
(protocol_clone, protocol_index, client_result, elapsed)
});
if let Ok(mut guard) = inflight.lock() {
guard.push(handle);
}
}
}
async fn establish_protocol_network(
protocol: TransportProtocol,
config: ClientConfig,
core: ClientCore,
priority: usize,
) -> Result<(Box<dyn Client>, Duration)> {
match protocol {
TransportProtocol::WebSocket => {
#[cfg(feature = "websocket")]
{
let (client, elapsed) =
Self::establish_websocket_network(config, core, priority).await?;
Ok((Box::new(client), elapsed))
}
#[cfg(not(feature = "websocket"))]
{
let _ = (config, core, priority);
Err(Self::transport_feature_disabled(
TransportProtocol::WebSocket,
))
}
}
TransportProtocol::QUIC => {
#[cfg(feature = "quic")]
{
let (client, elapsed) =
Self::establish_quic_network(config, core, priority).await?;
Ok((Box::new(client), elapsed))
}
#[cfg(not(feature = "quic"))]
{
let _ = (config, core, priority);
Err(Self::transport_feature_disabled(TransportProtocol::QUIC))
}
}
TransportProtocol::TCP => {
#[cfg(feature = "tcp")]
{
let (client, elapsed) =
Self::establish_tcp_network(config, core, priority).await?;
Ok((Box::new(client), elapsed))
}
#[cfg(not(feature = "tcp"))]
{
let _ = (config, core, priority);
Err(Self::transport_feature_disabled(TransportProtocol::TCP))
}
}
}
}
#[cfg(feature = "websocket")]
async fn establish_websocket_network(
config: ClientConfig,
core: ClientCore,
priority: usize,
) -> Result<(WebSocketClient, Duration)> {
let start_time = monotonic_now();
let mut client = WebSocketClient::with_core(config, core);
let _connection_arc = client.establish_network_connection().await?;
let elapsed = start_time.elapsed();
tracing::debug!(
"WebSocket 网络连接建立成功 (优先级: {}, 耗时: {:?})",
priority,
elapsed
);
Ok((client, elapsed))
}
#[cfg(feature = "quic")]
async fn establish_quic_network(
config: ClientConfig,
core: ClientCore,
priority: usize,
) -> Result<(QUICClient, Duration)> {
let (endpoint, client_config) = match QUICClient::create_quic_endpoint_with_tls(&config.tls)
{
Ok(ep) => ep,
Err(e) => {
tracing::warn!(
"QUIC endpoint 创建失败 (优先级: {}, 地址: {}): {}",
priority,
config.server_url,
e
);
return Err(e);
}
};
let start_time = monotonic_now();
let mut client = match QUICClient::with_core_and_endpoint(
config.clone(),
core,
Some((endpoint, client_config)),
) {
Ok(client) => client,
Err(e) => {
let elapsed = start_time.elapsed();
tracing::warn!(
"QUIC 客户端创建失败 (优先级: {}, 耗时: {:?}, 地址: {}): {}",
priority,
elapsed,
config.server_url,
e
);
return Err(e);
}
};
let _connection_arc = client.establish_network_connection().await?;
let elapsed = start_time.elapsed();
tracing::debug!(
"QUIC 网络连接建立成功 (优先级: {}, 耗时: {:?})",
priority,
elapsed
);
Ok((client, elapsed))
}
#[cfg(feature = "tcp")]
async fn establish_tcp_network(
config: ClientConfig,
core: ClientCore,
priority: usize,
) -> Result<(TCPClient, Duration)> {
let start_time = monotonic_now();
let mut client = TCPClient::with_core(config, core);
let _connection_arc = client.establish_network_connection().await?;
let elapsed = start_time.elapsed();
tracing::debug!(
"TCP 网络连接建立成功 (优先级: {}, 耗时: {:?})",
priority,
elapsed
);
Ok((client, elapsed))
}
async fn cleanup_inflight(inflight: &RaceInflight) {
let handles = inflight
.lock()
.ok()
.map(|mut guard| std::mem::take(&mut *guard))
.unwrap_or_default();
if !handles.is_empty() {
Self::cleanup_remaining_race_handles(handles).await;
}
}
async fn wait_for_connections(
inflight: RaceInflight,
) -> (
Option<SuccessfulConnection>,
Vec<SuccessfulConnection>,
Vec<FailedConnection>,
) {
const TIME_THRESHOLD: Duration = Duration::from_millis(100);
let mut first_success: Option<SuccessfulConnection> = None;
let mut successful_clients = Vec::new();
let mut errors = Vec::new();
loop {
let batch = inflight
.lock()
.ok()
.map(|mut guard| std::mem::take(&mut *guard))
.unwrap_or_default();
if batch.is_empty() {
break;
}
let total_protocols = batch.len();
tracing::debug!("协议竞速 select 批次: {} 个任务", total_protocols);
let (result, _index, remaining) = select_all(batch).await;
if let Ok(mut guard) = inflight.lock() {
guard.extend(remaining);
}
match result {
Ok((protocol, protocol_index, client_result, elapsed)) => {
match client_result {
Ok(client) => {
if first_success.is_none() {
first_success = Some((protocol_index, protocol, client, elapsed));
let ms = elapsed.as_secs_f64() * 1000.0;
tracing::info!(
"🏆 第一个成功的连接: {:?} (优先级: {}, 耗时: {:.3}ms)",
protocol,
protocol_index,
ms
);
tracing::debug!("第一个连接成功,立即返回,不再等待其他连接");
break;
} else if let Some((_, _, _, first_elapsed)) = &first_success {
if elapsed <= *first_elapsed + TIME_THRESHOLD {
successful_clients.push((
protocol_index,
protocol,
client,
elapsed,
));
let ms = elapsed.as_secs_f64() * 1000.0;
tracing::debug!(
"⚡ 几乎同时成功的连接: {:?} (优先级: {}, 耗时: {:.3}ms)",
protocol,
protocol_index,
ms
);
} else {
let ms = elapsed.as_secs_f64() * 1000.0;
tracing::debug!(
"🐌 连接太慢,关闭: {:?} (优先级: {}, 耗时: {:.3}ms)",
protocol,
protocol_index,
ms
);
Self::disconnect_client_async(
protocol,
protocol_index,
elapsed,
);
}
}
}
Err(e) => {
let error_msg = e.to_string();
errors.push((protocol_index, protocol, e, elapsed));
let ms = elapsed.as_secs_f64() * 1000.0;
tracing::warn!(
"连接失败: {:?} (优先级: {}, 耗时: {:.3}ms): {}",
protocol,
protocol_index,
ms,
error_msg
);
}
}
}
Err(join_err) => {
tracing::error!("Task join error: {:?}", join_err);
}
}
}
Self::cleanup_inflight(&inflight).await;
(first_success, successful_clients, errors)
}
async fn cleanup_remaining_race_handles(
handles: Vec<tokio::task::JoinHandle<ConnectionTaskResult>>,
) {
for handle in handles {
match handle.await {
Ok((_protocol, _index, Ok(mut client), _elapsed)) => {
client.set_disconnect_requested(true);
if let Err(e) = client.disconnect().await {
tracing::debug!("[HybridClient] race cleanup disconnect: {}", e);
}
}
Ok((_protocol, _index, Err(_), _elapsed)) => {}
Err(join_err) => {
tracing::debug!("[HybridClient] race task join error: {:?}", join_err);
}
}
}
}
fn disconnect_client_async(protocol: TransportProtocol, priority: usize, elapsed: Duration) {
tracing::debug!(
"🐌 连接太慢,关闭: {:?} (优先级: {}, 耗时: {:?})",
protocol,
priority,
elapsed
);
}
async fn process_race_results(
first_success: Option<SuccessfulConnection>,
mut successful_clients: Vec<SuccessfulConnection>,
errors: Vec<FailedConnection>,
shared_core: ClientCore,
race_start: MonotonicInstant,
) -> Result<(Box<dyn Client>, TransportProtocol)> {
Self::log_protocol_timings(&first_success, &successful_clients, &errors);
if let Some((first_index, first_protocol, first_client, first_elapsed)) = first_success {
successful_clients.push((first_index, first_protocol, first_client, first_elapsed));
successful_clients.sort_by_key(|(index, _protocol, _client, _elapsed)| *index);
let (selected_index, selected_protocol, mut selected_client, selected_elapsed) =
successful_clients.remove(0);
let selected_ms = selected_elapsed.as_secs_f64() * 1000.0;
let total_ms = race_start.elapsed().as_secs_f64() * 1000.0;
tracing::info!(
"✅ 协议竞速成功: {:?} (优先级: {}, 网络连接耗时: {:.3}ms, 总竞速时间: {:.3}ms)",
selected_protocol,
selected_index,
selected_ms,
total_ms
);
Self::disconnect_unselected_clients(successful_clients).await;
tracing::debug!(
"📤 发送 CONNECT 消息: {:?} (优先级: {})",
selected_protocol,
selected_index
);
selected_client.connect().await?;
shared_core.state_manager.set_connected();
Ok((selected_client, selected_protocol))
} else {
Self::build_all_failed_error(errors)
}
}
fn log_protocol_timings(
first_success: &Option<SuccessfulConnection>,
successful_clients: &[SuccessfulConnection],
errors: &[FailedConnection],
) {
tracing::info!("📊 协议竞速耗时统计:");
let mut all_results: Vec<(usize, TransportProtocol, Option<Duration>, Option<String>)> =
Vec::new();
if let Some((index, protocol, _, elapsed)) = first_success {
all_results.push((*index, *protocol, Some(*elapsed), None));
}
for (index, protocol, _, elapsed) in successful_clients {
all_results.push((*index, *protocol, Some(*elapsed), None));
}
for (index, protocol, error, elapsed) in errors {
all_results.push((*index, *protocol, Some(*elapsed), Some(error.to_string())));
}
all_results.sort_by_key(|(index, _, _, _)| *index);
for (index, protocol, elapsed_opt, error_opt) in all_results {
let protocol_name = match protocol {
TransportProtocol::WebSocket => "WebSocket",
TransportProtocol::QUIC => "QUIC",
TransportProtocol::TCP => "TCP",
};
match (elapsed_opt, error_opt) {
(Some(elapsed), None) => {
let ms = elapsed.as_secs_f64() * 1000.0;
let is_first = first_success
.as_ref()
.map(|(idx, _, _, _)| *idx == index)
.unwrap_or(false);
if is_first {
tracing::info!(
" [{:2}] {:12} ✅ 成功 - {:.3}ms ⭐ (已选中)",
index,
protocol_name,
ms
);
} else {
tracing::info!(
" [{:2}] {:12} ✅ 成功 - {:.3}ms",
index,
protocol_name,
ms
);
}
}
(Some(elapsed), Some(err)) => {
let ms = elapsed.as_secs_f64() * 1000.0;
let err_short = if err.len() > 50 {
format!("{}...", &err[..50])
} else {
err
};
tracing::info!(
" [{:2}] {:12} ❌ 失败 - {:.3}ms - {}",
index,
protocol_name,
ms,
err_short
);
}
_ => {}
}
}
}
async fn disconnect_unselected_clients(clients: Vec<SuccessfulConnection>) {
if clients.is_empty() {
return;
}
tracing::info!(
"正在主动关闭 {} 个未选中的连接(客户端先断)...",
clients.len()
);
let mut handles = Vec::with_capacity(clients.len());
for (index, protocol, mut client, elapsed) in clients {
tracing::debug!(
"关闭未选中的连接: {:?} (优先级: {}, 耗时: {:?})",
protocol,
index,
elapsed
);
client.set_disconnect_requested(true);
let handle = tokio::spawn(async move {
let res = client.disconnect().await;
(protocol, res)
});
handles.push(handle);
}
for handle in handles {
if let Ok((protocol, res)) = handle.await {
match res {
Ok(()) => tracing::debug!("✅ {:?} 未选中连接已关闭", protocol),
Err(e) => tracing::warn!("关闭 {:?} 连接时出错: {}", protocol, e),
}
}
}
tracing::info!("所有未选中的连接已主动关闭");
}
fn build_all_failed_error(
errors: Vec<FailedConnection>,
) -> Result<(Box<dyn Client>, TransportProtocol)> {
let mut sorted_errors = errors;
sorted_errors.sort_by_key(|(index, _protocol, _error, _elapsed)| *index);
let error_details: Vec<String> = sorted_errors
.iter()
.map(|(index, protocol, e, elapsed)| {
format!("[{}] {:?} (耗时: {:?}): {}", index, protocol, elapsed, e)
})
.collect();
let error_msg = format!(
"所有协议连接都失败(按优先级顺序): {}",
error_details.join(", ")
);
tracing::error!("❌ {}", error_msg);
Err(FlareError::connection_failed(error_msg))
}
pub fn active_protocol(&self) -> TransportProtocol {
self.active_protocol
}
pub fn core(&self) -> &ClientCore {
&self.core
}
pub fn core_mut(&mut self) -> &mut ClientCore {
&mut self.core
}
}
#[async_trait]
impl Client for HybridClient {
async fn connect(&mut self) -> Result<()> {
let mut client = self.inner.lock().await;
client.connect().await
}
async fn disconnect(&mut self) -> Result<()> {
let mut client = self.inner.lock().await;
client.disconnect().await
}
async fn send_frame(&mut self, frame: &Frame) -> Result<()> {
let mut client = self.inner.lock().await;
client.send_frame(frame).await
}
fn is_connected(&self) -> bool {
match self.inner.try_lock() {
Ok(client) => client.is_connected(),
Err(_) => {
tokio::task::block_in_place(|| {
let client = self.inner.blocking_lock();
client.is_connected()
})
}
}
}
fn add_observer(&mut self, observer: ArcObserver) {
self.core.add_observer(observer);
}
fn remove_observer(&mut self, observer: ArcObserver) {
self.core.remove_observer(observer);
}
fn connection_id(&self) -> Option<String> {
if let Ok(client) = self.inner.try_lock() {
client.connection_id()
} else {
None
}
}
}
impl std::fmt::Debug for HybridClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HybridClient")
.field("active_protocol", &self.active_protocol)
.finish_non_exhaustive()
}
}
impl HybridClient {
pub async fn send_frame_and_wait(&mut self, frame: &Frame, timeout: Duration) -> Result<Frame> {
if frame.message_id.is_empty() {
return Err(FlareError::protocol_error(
"message_id is empty".to_string(),
));
}
tracing::debug!(
"[HybridClient] send_frame_and_wait: 注册等待响应, message_id={}, frame.message_id={}",
frame.message_id,
frame.message_id
);
let rx = self.core.register_pending_response(&frame.message_id).await;
tracing::debug!(
"[HybridClient] send_frame_and_wait: 已注册等待响应, message_id={}",
frame.message_id
);
{
let mut client = self.inner.lock().await;
client.send_frame(frame).await?;
}
tracing::debug!(
"[HybridClient] send_frame_and_wait: 消息已发送, 等待响应, message_id={}, timeout={:?}",
frame.message_id,
timeout
);
match platform_timeout(timeout, rx).await {
Ok(Ok(resp)) => {
tracing::debug!(
"[HybridClient] send_frame_and_wait: 收到响应, message_id={}, resp.message_id={}",
frame.message_id,
resp.message_id
);
Ok(resp)
}
Ok(Err(_)) => {
tracing::debug!(
"[HybridClient] send_frame_and_wait: 响应通道已关闭, message_id={}",
frame.message_id
);
self.core.cancel_pending_response(&frame.message_id).await;
Err(FlareError::protocol_error(
"Response channel closed".to_string(),
))
}
Err(_) => {
tracing::warn!(
"[HybridClient] send_frame_and_wait: 响应超时, message_id={}",
frame.message_id
);
self.core.cancel_pending_response(&frame.message_id).await;
Err(FlareError::protocol_error(format!(
"Response timeout for message_id {}",
frame.message_id
)))
}
}
}
pub async fn connect_with_config(config: ClientConfig) -> Result<Self> {
let mut client = Self::new(config)?;
client.connect().await?;
Ok(client)
}
pub async fn connect_with_race(config: ClientConfig) -> Result<Self> {
if !config.is_race_mode() {
return Self::connect_with_config(config).await;
}
let core = ClientCore::new(&config);
let race_timeout = config.race_timeout.unwrap_or(Duration::from_secs(5));
let inflight: RaceInflight = Arc::new(StdMutex::new(Vec::new()));
let inflight_cleanup = Arc::clone(&inflight);
let race_result = platform_timeout(
race_timeout,
Self::race_connect(config, core.clone(), inflight),
)
.await;
let (client, protocol) = match race_result {
Ok(result) => result?,
Err(_) => {
Self::cleanup_inflight(&inflight_cleanup).await;
return Err(FlareError::connection_failed(format!(
"Protocol race timed out after {:?}",
race_timeout
)));
}
};
Ok(Self {
inner: Arc::new(Mutex::new(client)),
active_protocol: protocol,
core,
})
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod hybrid_race_tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
struct TrackedRaceClient {
disconnected: Arc<AtomicBool>,
disconnect_requested: Arc<AtomicBool>,
}
impl TrackedRaceClient {
fn new(disconnected: Arc<AtomicBool>, disconnect_requested: Arc<AtomicBool>) -> Self {
Self {
disconnected,
disconnect_requested,
}
}
}
#[async_trait]
impl Client for TrackedRaceClient {
async fn connect(&mut self) -> Result<()> {
Ok(())
}
async fn disconnect(&mut self) -> Result<()> {
self.disconnected.store(true, Ordering::SeqCst);
Ok(())
}
async fn send_frame(&mut self, _frame: &Frame) -> Result<()> {
Ok(())
}
fn is_connected(&self) -> bool {
!self.disconnected.load(Ordering::SeqCst)
}
fn add_observer(&mut self, _observer: ArcObserver) {}
fn remove_observer(&mut self, _observer: ArcObserver) {}
fn set_disconnect_requested(&mut self, value: bool) {
self.disconnect_requested.store(value, Ordering::SeqCst);
}
}
fn ok_client(
disconnected: Arc<AtomicBool>,
disconnect_requested: Arc<AtomicBool>,
) -> Box<dyn Client> {
Box::new(TrackedRaceClient::new(disconnected, disconnect_requested))
}
#[tokio::test]
async fn cleanup_remaining_race_handles_disconnects_successful_clients() {
let disconnected = Arc::new(AtomicBool::new(false));
let disconnect_requested = Arc::new(AtomicBool::new(false));
let client = ok_client(Arc::clone(&disconnected), Arc::clone(&disconnect_requested));
let handle = tokio::spawn(async move {
(
TransportProtocol::QUIC,
1,
Ok(client),
Duration::from_millis(1),
)
});
HybridClient::cleanup_remaining_race_handles(vec![handle]).await;
assert!(disconnected.load(Ordering::SeqCst));
assert!(disconnect_requested.load(Ordering::SeqCst));
}
#[tokio::test]
async fn cleanup_remaining_race_handles_ignores_failed_clients() {
let handle = tokio::spawn(async move {
(
TransportProtocol::QUIC,
0,
Err(FlareError::connection_failed("mock failure".to_string())),
Duration::ZERO,
)
});
HybridClient::cleanup_remaining_race_handles(vec![handle]).await;
}
#[tokio::test]
async fn wait_for_connections_cleans_up_inflight_losers_after_early_win() {
let winner_disconnected = Arc::new(AtomicBool::new(false));
let winner_requested = Arc::new(AtomicBool::new(false));
let loser_disconnected = Arc::new(AtomicBool::new(false));
let loser_requested = Arc::new(AtomicBool::new(false));
let fast = tokio::spawn({
let winner_disconnected = Arc::clone(&winner_disconnected);
let winner_requested = Arc::clone(&winner_requested);
async move {
crate::common::platform::sleep(Duration::from_millis(5)).await;
(
TransportProtocol::WebSocket,
0,
Ok(ok_client(winner_disconnected, winner_requested)),
Duration::from_millis(5),
)
}
});
let slow = tokio::spawn({
let loser_disconnected = Arc::clone(&loser_disconnected);
let loser_requested = Arc::clone(&loser_requested);
async move {
crate::common::platform::sleep(Duration::from_millis(200)).await;
(
TransportProtocol::QUIC,
1,
Ok(ok_client(loser_disconnected, loser_requested)),
Duration::from_millis(200),
)
}
});
let (first, _also_fast, errors) = {
let inflight: RaceInflight = Arc::new(StdMutex::new(vec![fast, slow]));
HybridClient::wait_for_connections(inflight).await
};
assert!(first.is_some());
assert!(errors.is_empty());
assert!(
!winner_disconnected.load(Ordering::SeqCst),
"winner must not be disconnected during race cleanup"
);
assert!(
loser_disconnected.load(Ordering::SeqCst),
"in-flight loser must be disconnected after early win"
);
assert!(loser_requested.load(Ordering::SeqCst));
}
}