mirror of
https://github.com/telemt/telemt.git
synced 2026-09-15 15:04:15 +03:00
WEB: Lifecycle + Lane ownership + Diag fixes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -29,8 +29,10 @@ fn session() -> (Arc<WebSession>, Arc<WebProcessRuntime>) {
|
||||
max_streams: 1,
|
||||
max_streams_per_session: 1,
|
||||
});
|
||||
let mut timeouts = WebTimeoutsConfig::default();
|
||||
timeouts.long_poll_secs = 1;
|
||||
let timeouts = WebTimeoutsConfig {
|
||||
long_poll_secs: 1,
|
||||
..WebTimeoutsConfig::default()
|
||||
};
|
||||
let session = WebSession::new(
|
||||
Arc::downgrade(&manager),
|
||||
[1; 32],
|
||||
|
||||
@@ -6,8 +6,8 @@ use tokio::sync::OwnedSemaphorePermit;
|
||||
|
||||
use super::lane_downlink::take_lane_down_batch;
|
||||
use super::{
|
||||
PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState, WebSession,
|
||||
remember_closed,
|
||||
CarrierLaneIdentity, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState,
|
||||
WebSession, remember_closed,
|
||||
};
|
||||
use crate::web::frame::{self, FrameType};
|
||||
use crate::web::manager::ManagerError;
|
||||
@@ -18,15 +18,46 @@ impl WebSession {
|
||||
&self,
|
||||
lane_id: u32,
|
||||
cursor: u64,
|
||||
) -> Result<PollResult, ManagerError> {
|
||||
self.poll_down_lane_inner(lane_id, None, cursor).await
|
||||
}
|
||||
|
||||
/// Polls only the exact lane incarnation owned by one WebSocket driver.
|
||||
pub(crate) async fn poll_down_websocket_lane(
|
||||
&self,
|
||||
lane: CarrierLaneIdentity,
|
||||
cursor: u64,
|
||||
) -> Result<PollResult, ManagerError> {
|
||||
self.poll_down_lane_inner(lane.lane_id, Some(lane.instance), cursor)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn poll_down_lane_inner(
|
||||
&self,
|
||||
lane_id: u32,
|
||||
expected_instance: Option<u64>,
|
||||
cursor: u64,
|
||||
) -> Result<PollResult, ManagerError> {
|
||||
if !self.carrier().uses_lanes() || lane_id > frame::MAX_STREAM_ID {
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
if !self.wait_for_lane_open(lane_id, cursor).await? {
|
||||
let lane_ready = if let Some(expected_instance) = expected_instance {
|
||||
let state = self.state.lock();
|
||||
if state.closed {
|
||||
return Err(ManagerError::Closed);
|
||||
}
|
||||
state
|
||||
.carrier_lanes
|
||||
.get(&lane_id)
|
||||
.is_some_and(|lane| lane.instance == expected_instance)
|
||||
} else {
|
||||
self.wait_for_lane_open(lane_id, cursor).await?
|
||||
};
|
||||
if !lane_ready {
|
||||
return Ok(PollResult {
|
||||
body: Bytes::new(),
|
||||
next_cursor: cursor,
|
||||
lane_closed: false,
|
||||
lane_closed: expected_instance.is_some(),
|
||||
});
|
||||
}
|
||||
let (instance, epoch, notify, healthy) = {
|
||||
@@ -43,6 +74,13 @@ impl WebSession {
|
||||
lane_closed: true,
|
||||
});
|
||||
};
|
||||
if expected_instance.is_some_and(|instance| lane.instance != instance) {
|
||||
return Ok(PollResult {
|
||||
body: Bytes::new(),
|
||||
next_cursor: cursor,
|
||||
lane_closed: true,
|
||||
});
|
||||
}
|
||||
if let Some(unacked) = &lane.unacked {
|
||||
if cursor == unacked.base_cursor {
|
||||
return Ok(PollResult {
|
||||
|
||||
@@ -84,6 +84,11 @@ impl WebSession {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current number of registered logical-stream tasks.
|
||||
pub(crate) fn tasks_live(&self) -> usize {
|
||||
self.tasks_live.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Atomically closes a session only when reconnect grace is still due.
|
||||
pub(crate) fn close_if_due(&self, now: Instant) -> bool {
|
||||
let healthy = {
|
||||
|
||||
@@ -166,6 +166,8 @@ impl WebSession {
|
||||
result
|
||||
}
|
||||
|
||||
// Batch application keeps every transactional accumulator explicit.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn apply_batch_locked(
|
||||
self: &Arc<Self>,
|
||||
state: &mut SessionState,
|
||||
|
||||
+226
-65
@@ -4,7 +4,10 @@ use std::time::Instant;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use super::uplink::{AppliedProgress, inbound_reservation, validate_batch};
|
||||
use super::{PendingClass, WebSession, inbound_queue_cost, insert_carrier_lane};
|
||||
use super::{
|
||||
CarrierLaneIdentity, PendingClass, StreamIdentity, WebSession, WebSocketLaneClaim,
|
||||
inbound_queue_cost, insert_carrier_lane,
|
||||
};
|
||||
use crate::config::WebCarrier;
|
||||
use crate::web::frame;
|
||||
use crate::web::manager::ManagerError;
|
||||
@@ -12,9 +15,19 @@ use crate::web::manager::ManagerError;
|
||||
/// Pre-OPEN stream quota and synthetic tuple ownership for one WebSocket lane.
|
||||
pub(crate) struct WebSocketLaneReservation {
|
||||
session: Arc<WebSession>,
|
||||
lane_id: u32,
|
||||
peer_port: u16,
|
||||
transferred: bool,
|
||||
claim: WebSocketLaneClaim,
|
||||
stream: Option<StreamIdentity>,
|
||||
phase: WebSocketLaneReservationPhase,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum WebSocketLaneReservationPhase {
|
||||
Reserved,
|
||||
Bound,
|
||||
Transferred,
|
||||
StreamOwned,
|
||||
Closing,
|
||||
Released,
|
||||
}
|
||||
|
||||
/// Session-wide ownership of the only automatic WebSocket carrier probe.
|
||||
@@ -57,28 +70,99 @@ impl Drop for WebSocketProbeReservation {
|
||||
impl WebSocketLaneReservation {
|
||||
/// Returns the logical stream owned by this connection.
|
||||
pub(crate) fn lane_id(&self) -> u32 {
|
||||
self.lane_id
|
||||
self.claim.lane.lane_id
|
||||
}
|
||||
|
||||
fn transfer_to_stream(&mut self) {
|
||||
let removed = self
|
||||
.session
|
||||
.state
|
||||
.lock()
|
||||
.websocket_lane_reservations
|
||||
.remove(&self.lane_id);
|
||||
if removed == Some(self.peer_port) {
|
||||
self.transferred = true;
|
||||
/// Returns the exact lane incarnation owned by this connection.
|
||||
pub(crate) fn lane_identity(&self) -> CarrierLaneIdentity {
|
||||
self.claim.lane
|
||||
}
|
||||
|
||||
/// Binds this pre-upgrade reservation to one admitted process connection.
|
||||
pub(crate) fn bind(&mut self, connection_id: u64) -> Result<(), ManagerError> {
|
||||
if self.phase != WebSocketLaneReservationPhase::Reserved {
|
||||
return Err(ManagerError::Concurrent);
|
||||
}
|
||||
let mut state = self.session.state.lock();
|
||||
if state.closed
|
||||
|| state
|
||||
.carrier_lanes
|
||||
.get(&self.claim.lane.lane_id)
|
||||
.is_none_or(|lane| lane.instance != self.claim.lane.instance)
|
||||
{
|
||||
return Err(ManagerError::Closed);
|
||||
}
|
||||
let Some(current) = state
|
||||
.websocket_lane_reservations
|
||||
.get_mut(&self.claim.lane.lane_id)
|
||||
.filter(|current| **current == self.claim && current.connection_id.is_none())
|
||||
else {
|
||||
return Err(ManagerError::Closed);
|
||||
};
|
||||
current.connection_id = Some(connection_id);
|
||||
self.claim.connection_id = Some(connection_id);
|
||||
self.phase = WebSocketLaneReservationPhase::Bound;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transfer_to_stream(&mut self, stream: StreamIdentity) -> Result<(), ManagerError> {
|
||||
if self.phase != WebSocketLaneReservationPhase::Bound
|
||||
|| stream.id != self.claim.lane.lane_id
|
||||
{
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
let mut state = self.session.state.lock();
|
||||
if state
|
||||
.carrier_lanes
|
||||
.get(&self.claim.lane.lane_id)
|
||||
.is_none_or(|lane| lane.instance != self.claim.lane.instance)
|
||||
|| state
|
||||
.streams
|
||||
.get(&stream.id)
|
||||
.is_none_or(|current| current.instance != stream.instance)
|
||||
|| state
|
||||
.websocket_lane_reservations
|
||||
.get(&self.claim.lane.lane_id)
|
||||
!= Some(&self.claim)
|
||||
{
|
||||
return Err(ManagerError::Closed);
|
||||
}
|
||||
state
|
||||
.websocket_lane_reservations
|
||||
.remove(&self.claim.lane.lane_id);
|
||||
self.stream = Some(stream);
|
||||
self.phase = WebSocketLaneReservationPhase::Transferred;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_stream_owned(&mut self, stream: StreamIdentity) -> Result<(), ManagerError> {
|
||||
if self.phase != WebSocketLaneReservationPhase::Transferred || self.stream != Some(stream) {
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
self.phase = WebSocketLaneReservationPhase::StreamOwned;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn retain_after_rejected_spawn(&mut self) {
|
||||
debug_assert_eq!(self.phase, WebSocketLaneReservationPhase::StreamOwned);
|
||||
self.phase = WebSocketLaneReservationPhase::Transferred;
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
if self.phase == WebSocketLaneReservationPhase::Released {
|
||||
return;
|
||||
}
|
||||
let stream_owned = self.phase == WebSocketLaneReservationPhase::StreamOwned;
|
||||
self.phase = WebSocketLaneReservationPhase::Closing;
|
||||
self.session
|
||||
.release_websocket_lane_claim(self.claim, self.stream, stream_owned);
|
||||
self.phase = WebSocketLaneReservationPhase::Released;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WebSocketLaneReservation {
|
||||
fn drop(&mut self) {
|
||||
if !self.transferred {
|
||||
self.session
|
||||
.release_websocket_lane_reservation(self.lane_id, self.peer_port);
|
||||
}
|
||||
self.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +246,7 @@ impl WebSession {
|
||||
);
|
||||
return Err(ManagerError::Limit);
|
||||
}
|
||||
state.websocket_lane_reservations.insert(lane_id, peer_port);
|
||||
if insert_carrier_lane(&mut state, lane_id).is_none() {
|
||||
state.websocket_lane_reservations.remove(&lane_id);
|
||||
let Some(lane) = insert_carrier_lane(&mut state, lane_id) else {
|
||||
state.active_peer_ports.remove(&peer_port);
|
||||
manager.release_stream(
|
||||
self.profile_key,
|
||||
@@ -173,14 +255,37 @@ impl WebSession {
|
||||
peer_port,
|
||||
);
|
||||
return Err(ManagerError::Protocol);
|
||||
};
|
||||
let claim = WebSocketLaneClaim {
|
||||
lane,
|
||||
peer_port,
|
||||
connection_id: None,
|
||||
};
|
||||
let inserted = match state.websocket_lane_reservations.entry(lane_id) {
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(claim);
|
||||
true
|
||||
}
|
||||
std::collections::hash_map::Entry::Occupied(_) => false,
|
||||
};
|
||||
if !inserted {
|
||||
self.release_lane_locked(&mut state, lane_id);
|
||||
state.active_peer_ports.remove(&peer_port);
|
||||
manager.release_stream(
|
||||
self.profile_key,
|
||||
self.client_ip,
|
||||
self.profile.public_addr,
|
||||
peer_port,
|
||||
);
|
||||
return Err(ManagerError::Concurrent);
|
||||
}
|
||||
drop(state);
|
||||
self.lane_open_notify.notify_waiters();
|
||||
Ok(WebSocketLaneReservation {
|
||||
session: Arc::clone(self),
|
||||
lane_id,
|
||||
peer_port,
|
||||
transferred: false,
|
||||
claim,
|
||||
stream: None,
|
||||
phase: WebSocketLaneReservationPhase::Reserved,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,12 +297,16 @@ impl WebSession {
|
||||
body: &[u8],
|
||||
) -> Result<bool, ManagerError> {
|
||||
if !Arc::ptr_eq(self, &reservation.session)
|
||||
|| reservation.lane_id == 0
|
||||
|| reservation.lane_id > frame::MAX_STREAM_ID
|
||||
|| reservation.lane_id() == 0
|
||||
|| reservation.lane_id() > frame::MAX_STREAM_ID
|
||||
|| !matches!(
|
||||
reservation.phase,
|
||||
WebSocketLaneReservationPhase::Bound | WebSocketLaneReservationPhase::StreamOwned
|
||||
)
|
||||
{
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
let lane_id = reservation.lane_id;
|
||||
let lane_id = reservation.lane_id();
|
||||
let frames = frame::parse_all(body, &self.limits).map_err(|_| ManagerError::Protocol)?;
|
||||
if frames
|
||||
.iter()
|
||||
@@ -216,8 +325,19 @@ impl WebSession {
|
||||
return Err(ManagerError::Closed);
|
||||
}
|
||||
self.ensure_carrier_active_locked(&state)?;
|
||||
if !reservation.transferred
|
||||
&& state.websocket_lane_reservations.get(&lane_id) != Some(&reservation.peer_port)
|
||||
if state
|
||||
.carrier_lanes
|
||||
.get(&lane_id)
|
||||
.is_none_or(|lane| lane.instance != reservation.claim.lane.instance)
|
||||
|| (reservation.phase == WebSocketLaneReservationPhase::Bound
|
||||
&& state.websocket_lane_reservations.get(&lane_id) != Some(&reservation.claim))
|
||||
|| (reservation.phase == WebSocketLaneReservationPhase::StreamOwned
|
||||
&& reservation.stream.is_none_or(|stream| {
|
||||
state
|
||||
.streams
|
||||
.get(&stream.id)
|
||||
.is_none_or(|current| current.instance != stream.instance)
|
||||
}))
|
||||
{
|
||||
return Err(ManagerError::Closed);
|
||||
}
|
||||
@@ -252,8 +372,8 @@ impl WebSession {
|
||||
let mut unused_bytes = reserve_bytes;
|
||||
let mut unused_items = reserve_items;
|
||||
let mut progress = AppliedProgress::default();
|
||||
let mut reserved_open =
|
||||
(!reservation.transferred).then_some((lane_id, reservation.peer_port));
|
||||
let mut reserved_open = (reservation.phase == WebSocketLaneReservationPhase::Bound)
|
||||
.then_some((lane_id, reservation.claim.peer_port));
|
||||
let applied = self.apply_batch_locked(
|
||||
&mut state,
|
||||
&frames,
|
||||
@@ -287,15 +407,18 @@ impl WebSession {
|
||||
self.finish_carrier_health();
|
||||
}
|
||||
for completion in opened {
|
||||
if completion.stream.id != lane_id || completion.peer_port != reservation.peer_port {
|
||||
let stream = completion.stream;
|
||||
if stream.id != lane_id || completion.peer_port != reservation.claim.peer_port {
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
reservation.transfer_to_stream(stream)?;
|
||||
reservation.mark_stream_owned(stream)?;
|
||||
if !self.spawn_stream(completion, true) {
|
||||
reservation.retain_after_rejected_spawn();
|
||||
return Err(ManagerError::Limit);
|
||||
}
|
||||
reservation.transfer_to_stream();
|
||||
}
|
||||
if !reservation.transferred {
|
||||
if reservation.phase != WebSocketLaneReservationPhase::StreamOwned {
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
if let Some(manager) = self.manager.upgrade() {
|
||||
@@ -304,49 +427,87 @@ impl WebSession {
|
||||
Ok(progressed)
|
||||
}
|
||||
|
||||
/// Ends one failed or disconnected lane without closing its parent session.
|
||||
pub(crate) fn close_websocket_lane(&self, lane_id: u32) {
|
||||
let reserved = {
|
||||
let mut state = self.state.lock();
|
||||
let reserved = state.websocket_lane_reservations.remove(&lane_id);
|
||||
if let Some(stream) = state.streams.remove(&lane_id) {
|
||||
state.closing_streams.insert(lane_id, stream.instance);
|
||||
let (bytes, items) = inbound_queue_cost(&stream.inbound);
|
||||
self.release_locked(&mut state, bytes, items, false);
|
||||
if let Some(waker) = stream.read_waker {
|
||||
waker.wake();
|
||||
}
|
||||
if let Some(waker) = stream.write_waker {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
self.remember_closed_locked(&mut state, lane_id);
|
||||
self.release_lane_locked(&mut state, lane_id);
|
||||
reserved
|
||||
};
|
||||
if let Some(peer_port) = reserved {
|
||||
self.release_websocket_lane_reservation(lane_id, peer_port);
|
||||
/// Ends one exact failed or disconnected lane without closing its parent session.
|
||||
pub(crate) fn close_websocket_lane(&self, mut reservation: WebSocketLaneReservation) {
|
||||
if std::ptr::eq(self, Arc::as_ptr(&reservation.session)) {
|
||||
reservation.release();
|
||||
}
|
||||
self.lane_open_notify.notify_waiters();
|
||||
}
|
||||
|
||||
fn release_websocket_lane_reservation(&self, lane_id: u32, peer_port: u16) {
|
||||
let removed = {
|
||||
fn release_websocket_lane_claim(
|
||||
&self,
|
||||
claim: WebSocketLaneClaim,
|
||||
stream: Option<StreamIdentity>,
|
||||
stream_owned: bool,
|
||||
) {
|
||||
let release_port = {
|
||||
let mut state = self.state.lock();
|
||||
if state.websocket_lane_reservations.get(&lane_id) == Some(&peer_port) {
|
||||
state.websocket_lane_reservations.remove(&lane_id);
|
||||
let lane_matches = state
|
||||
.carrier_lanes
|
||||
.get(&claim.lane.lane_id)
|
||||
.is_some_and(|lane| lane.instance == claim.lane.instance);
|
||||
if !lane_matches && !state.closed {
|
||||
return;
|
||||
}
|
||||
self.release_lane_locked(&mut state, lane_id);
|
||||
state.active_peer_ports.remove(&peer_port)
|
||||
let release_port = if let Some(stream) = stream {
|
||||
if stream.id != claim.lane.lane_id {
|
||||
return;
|
||||
}
|
||||
let current_stream = state
|
||||
.streams
|
||||
.get(&stream.id)
|
||||
.is_some_and(|current| current.instance == stream.instance);
|
||||
if current_stream {
|
||||
let Some(stream_state) = state.streams.remove(&stream.id) else {
|
||||
return;
|
||||
};
|
||||
state
|
||||
.closing_streams
|
||||
.insert(claim.lane.lane_id, stream.instance);
|
||||
let (bytes, items) = inbound_queue_cost(&stream_state.inbound);
|
||||
self.release_locked(&mut state, bytes, items, false);
|
||||
if let Some(waker) = stream_state.read_waker {
|
||||
waker.wake();
|
||||
}
|
||||
if let Some(waker) = stream_state.write_waker {
|
||||
waker.wake();
|
||||
}
|
||||
false
|
||||
} else if stream_owned {
|
||||
false
|
||||
} else {
|
||||
state.active_peer_ports.remove(&claim.peer_port)
|
||||
}
|
||||
} else {
|
||||
if state.websocket_lane_reservations.get(&claim.lane.lane_id) != Some(&claim) {
|
||||
return;
|
||||
}
|
||||
state
|
||||
.websocket_lane_reservations
|
||||
.remove(&claim.lane.lane_id);
|
||||
state.active_peer_ports.remove(&claim.peer_port)
|
||||
};
|
||||
if lane_matches {
|
||||
self.remember_closed_locked(&mut state, claim.lane.lane_id);
|
||||
if state
|
||||
.carrier_lanes
|
||||
.get(&claim.lane.lane_id)
|
||||
.is_some_and(|lane| lane.instance == claim.lane.instance)
|
||||
{
|
||||
self.release_lane_locked(&mut state, claim.lane.lane_id);
|
||||
}
|
||||
}
|
||||
release_port
|
||||
};
|
||||
if removed && let Some(manager) = self.manager.upgrade() {
|
||||
if release_port && let Some(manager) = self.manager.upgrade() {
|
||||
manager.release_stream(
|
||||
self.profile_key,
|
||||
self.client_ip,
|
||||
self.profile.public_addr,
|
||||
peer_port,
|
||||
claim.peer_port,
|
||||
);
|
||||
}
|
||||
self.lane_open_notify.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user