WEB: Lifecycle + Lane ownership + Diag fixes

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-08-27 09:02:19 +03:00
parent c75cf5cc9d
commit f73f52a033
25 changed files with 2050 additions and 245 deletions
+285 -3
View File
@@ -81,10 +81,48 @@ fn runtime(admission: bool) -> TestRuntime {
}
}
fn detach_stale_lane(runtime: &TestRuntime, reservation: &WebSocketLaneReservation) {
let claim = reservation.claim;
{
let mut state = runtime.session.state.lock();
assert_eq!(
state
.websocket_lane_reservations
.remove(&claim.lane.lane_id),
Some(claim)
);
runtime
.session
.release_lane_locked(&mut state, claim.lane.lane_id);
assert!(state.active_peer_ports.remove(&claim.peer_port));
}
runtime.manager.release_stream(
runtime.session.profile_key,
runtime.session.client_ip,
runtime.session.profile.public_addr,
claim.peer_port,
);
}
fn replacement_lane(
runtime: &TestRuntime,
stale: &WebSocketLaneReservation,
) -> WebSocketLaneReservation {
detach_stale_lane(runtime, stale);
let mut replacement = runtime
.session
.reserve_websocket_lane(stale.lane_id())
.unwrap();
replacement.bind(2).unwrap();
assert_ne!(replacement.lane_identity(), stale.lane_identity());
replacement
}
#[tokio::test]
async fn rejected_open_retains_stream_quota_until_lane_socket_teardown() {
let runtime = runtime(false);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
reservation.bind(1).unwrap();
let open = frame::encode(FrameType::Open, 7, &[]);
assert_eq!(
@@ -93,6 +131,19 @@ async fn rejected_open_retains_stream_quota_until_lane_socket_teardown() {
.process_websocket_lane(&mut reservation, 1, &open),
Err(ManagerError::Limit),
);
assert_eq!(
reservation.phase,
WebSocketLaneReservationPhase::Transferred
);
assert!(reservation.stream.is_some());
assert!(
!runtime
.session
.state
.lock()
.websocket_lane_reservations
.contains_key(&7)
);
assert!(
runtime
.manager
@@ -105,8 +156,124 @@ async fn rejected_open_retains_stream_quota_until_lane_socket_teardown() {
.is_none()
);
runtime.session.close_websocket_lane(7);
runtime.session.close_websocket_lane(reservation);
let peer_port = runtime
.manager
.try_acquire_stream(
runtime.session.profile_key,
runtime.session.profile.max_streams,
runtime.session.client_ip,
runtime.session.profile.public_addr,
)
.unwrap();
runtime.manager.release_stream(
runtime.session.profile_key,
runtime.session.client_ip,
runtime.session.profile.public_addr,
peer_port,
);
runtime.shutdown().await;
}
#[tokio::test]
async fn closed_session_releases_bound_lane_quota_on_reservation_drop() {
let runtime = runtime(true);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
reservation.bind(1).unwrap();
runtime.session.close();
drop(reservation);
assert!(runtime.session.state.lock().active_peer_ports.is_empty());
let peer_port = runtime
.manager
.try_acquire_stream(
runtime.session.profile_key,
runtime.session.profile.max_streams,
runtime.session.client_ip,
runtime.session.profile.public_addr,
)
.unwrap();
runtime.manager.release_stream(
runtime.session.profile_key,
runtime.session.client_ip,
runtime.session.profile.public_addr,
peer_port,
);
runtime.shutdown().await;
}
#[tokio::test]
async fn closed_session_releases_transferred_rejected_lane_quota() {
let runtime = runtime(false);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
reservation.bind(1).unwrap();
let open = frame::encode(FrameType::Open, 7, &[]);
assert_eq!(
runtime
.session
.process_websocket_lane(&mut reservation, 1, &open),
Err(ManagerError::Limit),
);
assert_eq!(
reservation.phase,
WebSocketLaneReservationPhase::Transferred
);
runtime.session.close();
drop(reservation);
assert!(runtime.session.state.lock().active_peer_ports.is_empty());
let peer_port = runtime
.manager
.try_acquire_stream(
runtime.session.profile_key,
runtime.session.profile.max_streams,
runtime.session.client_ip,
runtime.session.profile.public_addr,
)
.unwrap();
runtime.manager.release_stream(
runtime.session.profile_key,
runtime.session.client_ip,
runtime.session.profile.public_addr,
peer_port,
);
runtime.shutdown().await;
}
#[tokio::test]
async fn closed_session_keeps_stream_owned_quota_until_task_completion() {
let runtime = runtime(true);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
reservation.bind(1).unwrap();
let open = frame::encode(FrameType::Open, 7, &[]);
assert_eq!(
runtime
.session
.process_websocket_lane(&mut reservation, 1, &open),
Ok(true),
);
assert_eq!(
reservation.phase,
WebSocketLaneReservationPhase::StreamOwned
);
runtime.session.close();
drop(reservation);
assert!(
runtime
.manager
.try_acquire_stream(
runtime.session.profile_key,
runtime.session.profile.max_streams,
runtime.session.client_ip,
runtime.session.profile.public_addr,
)
.is_none()
);
runtime.session.wait().await;
let peer_port = runtime
.manager
.try_acquire_stream(
@@ -129,6 +296,7 @@ async fn rejected_open_retains_stream_quota_until_lane_socket_teardown() {
async fn malformed_lane_message_does_not_close_sibling_session_state() {
let runtime = runtime(true);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
reservation.bind(1).unwrap();
let data = frame::encode(FrameType::Data, 7, &[1]);
assert_eq!(
@@ -139,8 +307,122 @@ async fn malformed_lane_message_does_not_close_sibling_session_state() {
);
assert!(!runtime.session.state.lock().closed);
runtime.session.close_websocket_lane(7);
drop(reservation);
runtime.session.close_websocket_lane(reservation);
assert!(runtime.session.reserve_websocket_lane(8).is_ok());
runtime.shutdown().await;
}
#[tokio::test]
async fn stale_websocket_poll_does_not_close_reused_lane_instance() {
let runtime = runtime(true);
let mut stale = runtime.session.reserve_websocket_lane(7).unwrap();
stale.bind(1).unwrap();
let stale_identity = stale.lane_identity();
let replacement = replacement_lane(&runtime, &stale);
let result = runtime
.session
.poll_down_websocket_lane(stale_identity, u64::MAX)
.await
.unwrap();
assert!(result.lane_closed);
assert!(!runtime.session.state.lock().closed);
assert_eq!(
runtime
.session
.state
.lock()
.carrier_lanes
.get(&7)
.map(|lane| lane.instance),
Some(replacement.lane_identity().instance)
);
drop(stale);
runtime.session.close_websocket_lane(replacement);
runtime.shutdown().await;
}
#[tokio::test]
async fn stale_close_preserves_replacement_lane_and_tuple() {
let runtime = runtime(true);
let mut stale = runtime.session.reserve_websocket_lane(7).unwrap();
stale.bind(1).unwrap();
let replacement = replacement_lane(&runtime, &stale);
let replacement_claim = replacement.claim;
stale.phase = WebSocketLaneReservationPhase::Transferred;
stale.stream = Some(StreamIdentity { id: 7, instance: 1 });
runtime.session.close_websocket_lane(stale);
{
let state = runtime.session.state.lock();
assert_eq!(
state.websocket_lane_reservations.get(&7),
Some(&replacement_claim)
);
assert!(
state
.active_peer_ports
.contains(&replacement_claim.peer_port)
);
assert_eq!(
state.carrier_lanes.get(&7).map(|lane| lane.instance),
Some(replacement_claim.lane.instance)
);
}
runtime.session.close_websocket_lane(replacement);
runtime.shutdown().await;
}
#[tokio::test]
async fn stale_reservation_drop_preserves_replacement_claim() {
let runtime = runtime(true);
let mut stale = runtime.session.reserve_websocket_lane(7).unwrap();
stale.bind(1).unwrap();
let replacement = replacement_lane(&runtime, &stale);
let replacement_claim = replacement.claim;
drop(stale);
{
let state = runtime.session.state.lock();
assert_eq!(
state.websocket_lane_reservations.get(&7),
Some(&replacement_claim)
);
assert!(
state
.active_peer_ports
.contains(&replacement_claim.peer_port)
);
}
runtime.session.close_websocket_lane(replacement);
runtime.shutdown().await;
}
#[tokio::test]
async fn stale_transfer_cannot_remove_current_reservation() {
let runtime = runtime(true);
let mut stale = runtime.session.reserve_websocket_lane(7).unwrap();
stale.bind(1).unwrap();
let replacement = replacement_lane(&runtime, &stale);
let replacement_claim = replacement.claim;
assert_eq!(
stale.transfer_to_stream(StreamIdentity { id: 7, instance: 1 }),
Err(ManagerError::Closed)
);
assert_eq!(
runtime
.session
.state
.lock()
.websocket_lane_reservations
.get(&7),
Some(&replacement_claim)
);
drop(stale);
runtime.session.close_websocket_lane(replacement);
runtime.shutdown().await;
}