Bounded Debugging + Websocket Carriers + Carriers Negotiation

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-08-26 17:00:20 +03:00
parent 43cd84aaa5
commit 923c79796a
52 changed files with 3450 additions and 980 deletions
+13 -4
View File
@@ -70,6 +70,10 @@ fn test_runtime_with_dc(
user: "default".to_string(),
secret_mode: WebSecretMode::Plain,
carrier,
carrier_negotiation_enabled: false,
carrier_learning: true,
carriers: Arc::from([carrier]),
carrier_negotiation_deadlines_secs: [3, 5, 8, 12],
capability: [7; 32],
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 4,
@@ -120,6 +124,10 @@ fn test_runtime_with_dc(
1,
profile,
[7; 32],
carrier,
1,
[9; 32],
None,
limits,
timeouts,
);
@@ -323,10 +331,11 @@ async fn silent_streams_do_not_consume_active_handshake_capacity() {
assert_eq!(runtime.process_frame(2, 4, FrameType::Data, &[2]), Ok(4));
settle_tasks().await;
let state = runtime.session.state.lock();
assert!(state.streams.contains_key(&1));
assert!(!state.streams.contains_key(&2));
drop(state);
{
let state = runtime.session.state.lock();
assert!(state.streams.contains_key(&1));
assert!(!state.streams.contains_key(&2));
}
runtime.shutdown().await;
}
+8
View File
@@ -426,6 +426,10 @@ mod tests {
user: "alice".to_string(),
secret_mode: WebSecretMode::Plain,
carrier: WebCarrier::Https,
carrier_negotiation_enabled: false,
carrier_learning: true,
carriers: Arc::from([WebCarrier::Https]),
carrier_negotiation_deadlines_secs: [3, 5, 8, 12],
capability: [0; 32],
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 1,
@@ -439,6 +443,10 @@ mod tests {
1,
profile,
[2; 32],
WebCarrier::Https,
1,
[3; 32],
None,
WebLimitsConfig::default(),
WebTimeoutsConfig::default(),
)
+11
View File
@@ -41,12 +41,17 @@ impl WebSession {
return Err(ManagerError::Protocol);
}
let digest: TokenHash = Sha256::digest(body).into();
let progress = frames
.iter()
.any(|frame| matches!(frame.frame_type, FrameType::Open | FrameType::Data));
let mut opened = Vec::new();
let mut committed = false;
let result = {
let mut state = self.state.lock();
if state.closed {
return Err(ManagerError::Closed);
}
self.ensure_carrier_active_locked(&state)?;
state.last_activity = Instant::now();
if !state.carrier_lanes.contains_key(&lane_id) {
if lane_id != 0
@@ -125,6 +130,9 @@ impl WebSession {
lane.last_up_digest = digest;
}
}
if applied {
committed = self.commit_carrier_locked(&mut state, progress);
}
applied.then_some(sequence).ok_or(ManagerError::Closed)
};
if matches!(result, Err(ManagerError::Backpressure)) {
@@ -137,6 +145,9 @@ impl WebSession {
}
return result;
}
if committed {
self.finish_carrier_commit();
}
for (stream_id, peer_port) in opened {
self.spawn_stream(stream_id, peer_port, false);
}
+8
View File
@@ -14,6 +14,10 @@ fn session_with_limits(limits: WebLimitsConfig) -> Arc<WebSession> {
user: "alice".to_string(),
secret_mode: WebSecretMode::Plain,
carrier: WebCarrier::HttpsLanes,
carrier_negotiation_enabled: false,
carrier_learning: true,
carriers: Arc::from([WebCarrier::HttpsLanes]),
carrier_negotiation_deadlines_secs: [3, 5, 8, 12],
capability: [0; 32],
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 1,
@@ -27,6 +31,10 @@ fn session_with_limits(limits: WebLimitsConfig) -> Arc<WebSession> {
1,
profile,
[2; 32],
WebCarrier::HttpsLanes,
1,
[3; 32],
None,
limits,
WebTimeoutsConfig::default(),
)
+161
View File
@@ -0,0 +1,161 @@
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use super::{SessionNegotiationPhase, WebSession};
struct ReleasedQueues {
data_bytes: usize,
data_items: usize,
control_bytes: usize,
control_items: usize,
}
impl WebSession {
/// Closes carrier state while relay tasks retain their admission until exit.
pub(crate) fn close(&self) {
let Some(released) = self.begin_close(false) else {
return;
};
self.finish_close(released, false);
}
/// Atomically prevents first-frame commit while one successor is prepared.
pub(crate) fn begin_carrier_supersede(&self) -> bool {
let mut state = self.state.lock();
if state.closed || state.close_requested {
return false;
}
match state.negotiation_phase {
SessionNegotiationPhase::Uncommitted => {
state.negotiation_phase = SessionNegotiationPhase::Replacing;
true
}
SessionNegotiationPhase::Replacing
| SessionNegotiationPhase::Committed
| SessionNegotiationPhase::Superseded => false,
}
}
/// Restores an uncommitted attempt after successor admission failed.
pub(crate) fn cancel_carrier_supersede(&self) {
let close_requested = {
let mut state = self.state.lock();
if !state.closed && state.negotiation_phase == SessionNegotiationPhase::Replacing {
state.negotiation_phase = SessionNegotiationPhase::Uncommitted;
}
state.close_requested
};
if close_requested {
self.close();
}
}
/// Completes manager-owned replacement without unregistering the old session twice.
pub(crate) fn finish_carrier_supersede(&self) {
let Some(released) = self.begin_close(true) else {
return;
};
self.finish_close(released, true);
}
/// Waits for all logical-stream tasks after admission has closed.
pub(crate) async fn wait(&self) {
loop {
let notified = self.tasks_done.notified();
if self.tasks_live.load(Ordering::Acquire) == 0 {
return;
}
notified.await;
}
}
/// Returns whether reconnect grace elapsed without activity.
pub(crate) fn is_idle(&self, now: Instant) -> bool {
let state = self.state.lock();
!state.closed
&& state.negotiation_phase != SessionNegotiationPhase::Replacing
&& now.saturating_duration_since(state.last_activity)
>= Duration::from_secs(self.timeouts.reconnect_grace_secs)
}
fn begin_close(&self, superseded: bool) -> Option<ReleasedQueues> {
let mut state = self.state.lock();
if state.closed || (superseded && state.negotiation_phase != SessionNegotiationPhase::Replacing) {
return None;
}
if !superseded && state.negotiation_phase == SessionNegotiationPhase::Replacing {
state.close_requested = true;
return None;
}
state.closed = true;
if superseded {
state.negotiation_phase = SessionNegotiationPhase::Superseded;
}
for stream in state.streams.values_mut() {
if let Some(waker) = stream.read_waker.take() {
waker.wake();
}
if let Some(waker) = stream.write_waker.take() {
waker.wake();
}
}
state.streams.clear();
state.pending_frames.clear();
state.pending_windows.clear();
state.unacked = None;
for lane in state.carrier_lanes.values() {
lane.notify.notify_waiters();
}
state.carrier_lanes.clear();
let control_bytes = state.pending_control_bytes;
let control_items = state.pending_control_items;
let data_bytes = state.pending_bytes.saturating_sub(control_bytes);
let data_items = state.pending_items.saturating_sub(control_items);
state.pending_bytes = 0;
state.pending_items = 0;
state.pending_control_bytes = 0;
state.pending_control_items = 0;
Some(ReleasedQueues {
data_bytes,
data_items,
control_bytes,
control_items,
})
}
fn finish_close(&self, released: ReleasedQueues, superseded: bool) {
self.cancel.cancel();
if self.carrier().is_multiplexed() {
self.down_notify.notify_waiters();
}
if let Some(manager) = self.manager.upgrade() {
manager.release_pending(
self.profile_key,
released.data_bytes,
released.data_items,
false,
);
manager.release_pending(
self.profile_key,
released.control_bytes,
released.control_items,
true,
);
if !self.finished.swap(true, Ordering::AcqRel) {
self.trace_lifecycle(
crate::web::trace::TraceLifecycleEvent::SessionClosed,
None,
Some(if superseded { "superseded" } else { "closed" }),
);
if !superseded {
manager.session_finished(
self.token_hash,
self.client_ip,
self.profile_key,
&self.profile.host,
);
}
}
}
}
}
+17
View File
@@ -48,12 +48,17 @@ impl WebSession {
return Err(ManagerError::Protocol);
}
let digest: TokenHash = Sha256::digest(body).into();
let progress = frames
.iter()
.any(|frame| matches!(frame.frame_type, FrameType::Open | FrameType::Data));
let mut opened = Vec::new();
let mut committed = false;
let result = {
let mut state = self.state.lock();
if state.closed {
return Err(ManagerError::Closed);
}
self.ensure_carrier_active_locked(&state)?;
state.last_activity = Instant::now();
if sequence == state.last_up_sequence && sequence != 0 {
return if bool::from(state.last_up_digest.ct_eq(&digest)) {
@@ -99,6 +104,7 @@ impl WebSession {
} else {
state.last_up_sequence = sequence;
state.last_up_digest = digest;
committed = self.commit_carrier_locked(&mut state, progress);
Ok(sequence)
}
};
@@ -112,6 +118,9 @@ impl WebSession {
}
return result;
}
if committed {
self.finish_carrier_commit();
}
for (stream_id, peer_port) in opened {
self.spawn_stream(stream_id, peer_port, false);
}
@@ -362,6 +371,10 @@ mod tests {
user: "alice".to_string(),
secret_mode: WebSecretMode::Plain,
carrier: WebCarrier::Https,
carrier_negotiation_enabled: false,
carrier_learning: true,
carriers: Arc::from([WebCarrier::Https]),
carrier_negotiation_deadlines_secs: [3, 5, 8, 12],
capability: [0; 32],
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 1,
@@ -375,6 +388,10 @@ mod tests {
1,
profile,
[2; 32],
WebCarrier::Https,
1,
[3; 32],
None,
WebLimitsConfig::default(),
WebTimeoutsConfig::default(),
)
+11
View File
@@ -121,12 +121,17 @@ impl WebSession {
return Err(ManagerError::Protocol);
}
let digest = Sha256::digest(body).into();
let progress = frames
.iter()
.any(|frame| matches!(frame.frame_type, frame::FrameType::Open | frame::FrameType::Data));
let mut opened = Vec::new();
let mut committed = false;
let result = {
let mut state = self.state.lock();
if state.closed {
return Err(ManagerError::Closed);
}
self.ensure_carrier_active_locked(&state)?;
if !reservation.transferred
&& state.websocket_lane_reservations.get(&lane_id) != Some(&reservation.peer_port)
{
@@ -181,9 +186,15 @@ impl WebSession {
}
}
state.last_activity = Instant::now();
if applied {
committed = self.commit_carrier_locked(&mut state, progress);
}
applied.then_some(()).ok_or(ManagerError::Protocol)
};
result?;
if committed {
self.finish_carrier_commit();
}
for (stream_id, peer_port) in opened {
if stream_id != lane_id || peer_port != reservation.peer_port {
return Err(ManagerError::Protocol);
+8
View File
@@ -33,6 +33,10 @@ fn runtime(admission: bool) -> TestRuntime {
user: "default".to_string(),
secret_mode: WebSecretMode::Plain,
carrier: WebCarrier::WebsocketLanes,
carrier_negotiation_enabled: false,
carrier_learning: true,
carriers: Arc::from([WebCarrier::WebsocketLanes]),
carrier_negotiation_deadlines_secs: [3, 5, 8, 12],
capability: [7; 32],
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 2,
@@ -60,6 +64,10 @@ fn runtime(admission: bool) -> TestRuntime {
1,
profile,
[7; 32],
WebCarrier::WebsocketLanes,
1,
[9; 32],
None,
limits,
timeouts,
);