mirror of
https://github.com/telemt/telemt.git
synced 2026-09-05 18:16:06 +03:00
Pending Bytes&Items in Carrier Lanes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
+106
-23
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use tokio::sync::OwnedSemaphorePermit;
|
use tokio::sync::OwnedSemaphorePermit;
|
||||||
@@ -10,7 +10,7 @@ use tokio_util::sync::CancellationToken;
|
|||||||
use super::{ManagerError, ProfileKey, WebProcessRuntime, WebSocketBudgetLease};
|
use super::{ManagerError, ProfileKey, WebProcessRuntime, WebSocketBudgetLease};
|
||||||
|
|
||||||
/// One process-owned WebSocket carrier class used for eviction priority.
|
/// One process-owned WebSocket carrier class used for eviction priority.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub(crate) enum WebSocketKind {
|
pub(crate) enum WebSocketKind {
|
||||||
/// One connection multiplexes every logical stream in a session.
|
/// One connection multiplexes every logical stream in a session.
|
||||||
Multiplex,
|
Multiplex,
|
||||||
@@ -18,23 +18,42 @@ pub(crate) enum WebSocketKind {
|
|||||||
Lane(u32),
|
Lane(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
struct WebSocketClaimKey {
|
||||||
|
session_hash: super::TokenHash,
|
||||||
|
kind: WebSocketKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
enum WebSocketPhase {
|
||||||
|
Claimed,
|
||||||
|
Upgraded,
|
||||||
|
Active,
|
||||||
|
Closing,
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct WebSocketEntry {
|
pub(super) struct WebSocketEntry {
|
||||||
id: u64,
|
id: u64,
|
||||||
owner: ProfileKey,
|
owner: ProfileKey,
|
||||||
session_id: u64,
|
session_id: u64,
|
||||||
|
claim: WebSocketClaimKey,
|
||||||
client_ip: IpAddr,
|
client_ip: IpAddr,
|
||||||
kind: WebSocketKind,
|
kind: WebSocketKind,
|
||||||
liveness_interval_ms: u64,
|
liveness_interval_ms: u64,
|
||||||
created_tick: u64,
|
created_tick: u64,
|
||||||
last_peer_tick: AtomicU64,
|
last_peer_tick: AtomicU64,
|
||||||
last_progress_tick: AtomicU64,
|
last_progress_tick: AtomicU64,
|
||||||
opened: AtomicBool,
|
phase: AtomicU8,
|
||||||
|
closing: AtomicBool,
|
||||||
cancel: CancellationToken,
|
cancel: CancellationToken,
|
||||||
|
released: CancellationToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(super) struct WebSocketRegistry {
|
pub(super) struct WebSocketRegistry {
|
||||||
entries: HashMap<u64, Arc<WebSocketEntry>>,
|
entries: HashMap<u64, Arc<WebSocketEntry>>,
|
||||||
|
claims: HashMap<WebSocketClaimKey, u64>,
|
||||||
|
evictions_in_flight: usize,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,10 +83,20 @@ impl WebSocketConnection {
|
|||||||
|
|
||||||
/// Marks successful ownership transfer from HTTP to the WebSocket codec.
|
/// Marks successful ownership transfer from HTTP to the WebSocket codec.
|
||||||
pub(crate) fn mark_opened(&self) {
|
pub(crate) fn mark_opened(&self) {
|
||||||
self.entry.opened.store(true, Ordering::Release);
|
self.entry
|
||||||
|
.phase
|
||||||
|
.store(WebSocketPhase::Upgraded as u8, Ordering::Release);
|
||||||
self.mark_progress();
|
self.mark_progress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marks the first validated carrier binary message as active progress.
|
||||||
|
pub(crate) fn mark_active(&self) {
|
||||||
|
self.entry
|
||||||
|
.phase
|
||||||
|
.store(WebSocketPhase::Active as u8, Ordering::Release);
|
||||||
|
self.mark_peer_activity();
|
||||||
|
}
|
||||||
|
|
||||||
/// Refreshes the peer-liveness deadline after any received WebSocket message.
|
/// Refreshes the peer-liveness deadline after any received WebSocket message.
|
||||||
pub(crate) fn mark_peer_activity(&self) {
|
pub(crate) fn mark_peer_activity(&self) {
|
||||||
if let Some(runtime) = self.runtime.upgrade() {
|
if let Some(runtime) = self.runtime.upgrade() {
|
||||||
@@ -90,7 +119,16 @@ impl WebSocketConnection {
|
|||||||
impl Drop for WebSocketConnection {
|
impl Drop for WebSocketConnection {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(runtime) = self.runtime.upgrade() {
|
if let Some(runtime) = self.runtime.upgrade() {
|
||||||
runtime.websockets.lock().entries.remove(&self.entry.id);
|
let mut registry = runtime.websockets.lock();
|
||||||
|
registry.entries.remove(&self.entry.id);
|
||||||
|
if registry.claims.get(&self.entry.claim) == Some(&self.entry.id) {
|
||||||
|
registry.claims.remove(&self.entry.claim);
|
||||||
|
}
|
||||||
|
if self.entry.closing.load(Ordering::Acquire) {
|
||||||
|
registry.evictions_in_flight = registry.evictions_in_flight.saturating_sub(1);
|
||||||
|
}
|
||||||
|
drop(registry);
|
||||||
|
self.entry.released.cancel();
|
||||||
drop(self.base_budget.take());
|
drop(self.base_budget.take());
|
||||||
drop(self.slot.take());
|
drop(self.slot.take());
|
||||||
runtime.websocket_notify.notify_waiters();
|
runtime.websocket_notify.notify_waiters();
|
||||||
@@ -103,81 +141,126 @@ pub(super) async fn admit(
|
|||||||
runtime: &Arc<WebProcessRuntime>,
|
runtime: &Arc<WebProcessRuntime>,
|
||||||
owner: ProfileKey,
|
owner: ProfileKey,
|
||||||
session_id: u64,
|
session_id: u64,
|
||||||
|
session_hash: super::TokenHash,
|
||||||
client_ip: IpAddr,
|
client_ip: IpAddr,
|
||||||
kind: WebSocketKind,
|
kind: WebSocketKind,
|
||||||
base_bytes: usize,
|
base_bytes: usize,
|
||||||
liveness_interval: Duration,
|
liveness_interval: Duration,
|
||||||
eviction_timeout: Duration,
|
eviction_timeout: Duration,
|
||||||
|
parent_cancellation: CancellationToken,
|
||||||
) -> Result<WebSocketConnection, ManagerError> {
|
) -> Result<WebSocketConnection, ManagerError> {
|
||||||
let liveness_interval_ms = liveness_interval.as_millis().min(u128::from(u64::MAX)) as u64;
|
let liveness_interval_ms = liveness_interval.as_millis().min(u128::from(u64::MAX)) as u64;
|
||||||
if let Some(connection) = try_admit(
|
match try_admit(
|
||||||
runtime,
|
runtime,
|
||||||
owner,
|
owner,
|
||||||
session_id,
|
session_id,
|
||||||
|
session_hash,
|
||||||
client_ip,
|
client_ip,
|
||||||
kind,
|
kind,
|
||||||
base_bytes,
|
base_bytes,
|
||||||
liveness_interval_ms,
|
liveness_interval_ms,
|
||||||
|
&parent_cancellation,
|
||||||
) {
|
) {
|
||||||
return Ok(connection);
|
Ok(connection) => return Ok(connection),
|
||||||
|
Err(TryAdmitError::Conflict) => return Err(ManagerError::Concurrent),
|
||||||
|
Err(TryAdmitError::Closed) => return Err(ManagerError::Closed),
|
||||||
|
Err(TryAdmitError::Capacity) => {}
|
||||||
}
|
}
|
||||||
let Some(victim) = select_victim(runtime, owner, session_id, client_ip, None) else {
|
let Some(victim) = select_victim(runtime, owner, session_id, client_ip, None, true) else {
|
||||||
runtime.record_limit_hit();
|
runtime.record_limit_hit();
|
||||||
return Err(ManagerError::Limit);
|
return Err(ManagerError::Limit);
|
||||||
};
|
};
|
||||||
let released = runtime.websocket_notify.notified();
|
let released = victim.released.cancelled();
|
||||||
victim.cancel.cancel();
|
victim.cancel.cancel();
|
||||||
let _ = tokio::time::timeout(eviction_timeout, released).await;
|
let _ = tokio::time::timeout(eviction_timeout, released).await;
|
||||||
try_admit(
|
match try_admit(
|
||||||
runtime,
|
runtime,
|
||||||
owner,
|
owner,
|
||||||
session_id,
|
session_id,
|
||||||
|
session_hash,
|
||||||
client_ip,
|
client_ip,
|
||||||
kind,
|
kind,
|
||||||
base_bytes,
|
base_bytes,
|
||||||
liveness_interval_ms,
|
liveness_interval_ms,
|
||||||
)
|
&parent_cancellation,
|
||||||
.ok_or_else(|| {
|
) {
|
||||||
runtime.record_limit_hit();
|
Ok(connection) => Ok(connection),
|
||||||
ManagerError::Limit
|
Err(TryAdmitError::Conflict) => Err(ManagerError::Concurrent),
|
||||||
})
|
Err(TryAdmitError::Closed) => Err(ManagerError::Closed),
|
||||||
|
Err(TryAdmitError::Capacity) => {
|
||||||
|
runtime.record_limit_hit();
|
||||||
|
Err(ManagerError::Limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TryAdmitError {
|
||||||
|
Capacity,
|
||||||
|
Conflict,
|
||||||
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_admit(
|
fn try_admit(
|
||||||
runtime: &Arc<WebProcessRuntime>,
|
runtime: &Arc<WebProcessRuntime>,
|
||||||
owner: ProfileKey,
|
owner: ProfileKey,
|
||||||
session_id: u64,
|
session_id: u64,
|
||||||
|
session_hash: super::TokenHash,
|
||||||
client_ip: IpAddr,
|
client_ip: IpAddr,
|
||||||
kind: WebSocketKind,
|
kind: WebSocketKind,
|
||||||
base_bytes: usize,
|
base_bytes: usize,
|
||||||
liveness_interval_ms: u64,
|
liveness_interval_ms: u64,
|
||||||
) -> Option<WebSocketConnection> {
|
parent_cancellation: &CancellationToken,
|
||||||
|
) -> Result<WebSocketConnection, TryAdmitError> {
|
||||||
|
let claim = WebSocketClaimKey { session_hash, kind };
|
||||||
|
{
|
||||||
|
let registry = runtime.websockets.lock();
|
||||||
|
if registry.closed {
|
||||||
|
return Err(TryAdmitError::Closed);
|
||||||
|
}
|
||||||
|
if registry.claims.contains_key(&claim) {
|
||||||
|
return Err(TryAdmitError::Conflict);
|
||||||
|
}
|
||||||
|
}
|
||||||
let slot = Arc::clone(&runtime.websocket_connections)
|
let slot = Arc::clone(&runtime.websocket_connections)
|
||||||
.try_acquire_owned()
|
.try_acquire_owned()
|
||||||
.ok()?;
|
.map_err(|_| TryAdmitError::Capacity)?;
|
||||||
let base_budget = runtime.try_websocket_base_budget(owner, base_bytes)?;
|
let base_budget = runtime
|
||||||
let id = runtime.websocket_next_id.fetch_add(1, Ordering::Relaxed);
|
.try_websocket_base_budget(owner, base_bytes)
|
||||||
|
.ok_or(TryAdmitError::Capacity)?;
|
||||||
|
let id = runtime
|
||||||
|
.websocket_next_id
|
||||||
|
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| {
|
||||||
|
value.checked_add(1)
|
||||||
|
})
|
||||||
|
.map_err(|_| TryAdmitError::Capacity)?;
|
||||||
let now = runtime.websocket_tick();
|
let now = runtime.websocket_tick();
|
||||||
let entry = Arc::new(WebSocketEntry {
|
let entry = Arc::new(WebSocketEntry {
|
||||||
id,
|
id,
|
||||||
owner,
|
owner,
|
||||||
session_id,
|
session_id,
|
||||||
|
claim,
|
||||||
client_ip,
|
client_ip,
|
||||||
kind,
|
kind,
|
||||||
liveness_interval_ms,
|
liveness_interval_ms,
|
||||||
created_tick: now,
|
created_tick: now,
|
||||||
last_peer_tick: AtomicU64::new(now),
|
last_peer_tick: AtomicU64::new(now),
|
||||||
last_progress_tick: AtomicU64::new(now),
|
last_progress_tick: AtomicU64::new(now),
|
||||||
opened: AtomicBool::new(false),
|
phase: AtomicU8::new(WebSocketPhase::Claimed as u8),
|
||||||
cancel: CancellationToken::new(),
|
closing: AtomicBool::new(false),
|
||||||
|
cancel: parent_cancellation.child_token(),
|
||||||
|
released: CancellationToken::new(),
|
||||||
});
|
});
|
||||||
let mut registry = runtime.websockets.lock();
|
let mut registry = runtime.websockets.lock();
|
||||||
if registry.closed {
|
if registry.closed {
|
||||||
return None;
|
return Err(TryAdmitError::Closed);
|
||||||
}
|
}
|
||||||
|
if registry.claims.contains_key(&claim) {
|
||||||
|
return Err(TryAdmitError::Conflict);
|
||||||
|
}
|
||||||
|
registry.claims.insert(claim, id);
|
||||||
registry.entries.insert(id, Arc::clone(&entry));
|
registry.entries.insert(id, Arc::clone(&entry));
|
||||||
drop(registry);
|
drop(registry);
|
||||||
Some(WebSocketConnection {
|
Ok(WebSocketConnection {
|
||||||
runtime: Arc::downgrade(runtime),
|
runtime: Arc::downgrade(runtime),
|
||||||
entry,
|
entry,
|
||||||
slot: Some(slot),
|
slot: Some(slot),
|
||||||
|
|||||||
+4
-13
@@ -81,6 +81,8 @@ struct DownBatch {
|
|||||||
|
|
||||||
struct CarrierLane {
|
struct CarrierLane {
|
||||||
instance: u64,
|
instance: u64,
|
||||||
|
pending_bytes: usize,
|
||||||
|
pending_items: usize,
|
||||||
pending_frames: VecDeque<QueuedFrame>,
|
pending_frames: VecDeque<QueuedFrame>,
|
||||||
pending_windows: HashMap<u32, usize>,
|
pending_windows: HashMap<u32, usize>,
|
||||||
unacked: Option<DownBatch>,
|
unacked: Option<DownBatch>,
|
||||||
@@ -96,6 +98,8 @@ impl CarrierLane {
|
|||||||
fn new(instance: u64) -> Self {
|
fn new(instance: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
instance,
|
instance,
|
||||||
|
pending_bytes: 0,
|
||||||
|
pending_items: 0,
|
||||||
pending_frames: VecDeque::new(),
|
pending_frames: VecDeque::new(),
|
||||||
pending_windows: HashMap::new(),
|
pending_windows: HashMap::new(),
|
||||||
unacked: None,
|
unacked: None,
|
||||||
@@ -124,7 +128,6 @@ struct SessionState {
|
|||||||
last_up_sequence: u64,
|
last_up_sequence: u64,
|
||||||
last_up_digest: TokenHash,
|
last_up_digest: TokenHash,
|
||||||
carrier_lanes: HashMap<u32, CarrierLane>,
|
carrier_lanes: HashMap<u32, CarrierLane>,
|
||||||
lane_open_claims: HashSet<u32>,
|
|
||||||
lane_open_waits: usize,
|
lane_open_waits: usize,
|
||||||
next_lane_instance: u64,
|
next_lane_instance: u64,
|
||||||
websocket_lane_reservations: HashMap<u32, u16>,
|
websocket_lane_reservations: HashMap<u32, u16>,
|
||||||
@@ -231,7 +234,6 @@ impl WebSession {
|
|||||||
last_up_sequence: 0,
|
last_up_sequence: 0,
|
||||||
last_up_digest: [0; 32],
|
last_up_digest: [0; 32],
|
||||||
carrier_lanes,
|
carrier_lanes,
|
||||||
lane_open_claims: HashSet::new(),
|
|
||||||
lane_open_waits: 0,
|
lane_open_waits: 0,
|
||||||
next_lane_instance,
|
next_lane_instance,
|
||||||
websocket_lane_reservations: HashMap::new(),
|
websocket_lane_reservations: HashMap::new(),
|
||||||
@@ -458,17 +460,6 @@ impl WebSession {
|
|||||||
.map(|manager| manager.budget_notify())
|
.map(|manager| manager.budget_notify())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release_stream_reservation(&self, peer_port: u16) {
|
|
||||||
let removed = self.state.lock().active_peer_ports.remove(&peer_port);
|
|
||||||
if removed && let Some(manager) = self.manager.upgrade() {
|
|
||||||
manager.release_stream(
|
|
||||||
self.profile_key,
|
|
||||||
self.client_ip,
|
|
||||||
self.profile.public_addr,
|
|
||||||
peer_port,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inbound_queue_cost(queue: &VecDeque<InboundChunk>) -> (usize, usize) {
|
fn inbound_queue_cost(queue: &VecDeque<InboundChunk>) -> (usize, usize) {
|
||||||
|
|||||||
+144
-19
@@ -15,6 +15,12 @@ use crate::web::frame::{self, Frame, FrameType};
|
|||||||
use crate::web::manager::{ManagerError, TokenHash};
|
use crate::web::manager::{ManagerError, TokenHash};
|
||||||
|
|
||||||
impl WebSession {
|
impl WebSession {
|
||||||
|
/// Classifies control and pre-OPEN polls for their reserved handler pool.
|
||||||
|
pub(crate) fn lane_poll_is_auxiliary(&self, lane_id: u32) -> bool {
|
||||||
|
let state = self.state.lock();
|
||||||
|
lane_id == 0 || !state.carrier_lanes.contains_key(&lane_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies one exactly-once uplink batch to an independent HTTPS lane.
|
/// Applies one exactly-once uplink batch to an independent HTTPS lane.
|
||||||
pub(crate) fn process_up_lane(
|
pub(crate) fn process_up_lane(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
@@ -53,7 +59,8 @@ impl WebSession {
|
|||||||
}
|
}
|
||||||
self.ensure_carrier_active_locked(&state)?;
|
self.ensure_carrier_active_locked(&state)?;
|
||||||
state.last_activity = Instant::now();
|
state.last_activity = Instant::now();
|
||||||
if !state.carrier_lanes.contains_key(&lane_id) {
|
let new_lane = !state.carrier_lanes.contains_key(&lane_id);
|
||||||
|
if new_lane {
|
||||||
if lane_id != 0
|
if lane_id != 0
|
||||||
&& frames
|
&& frames
|
||||||
.first()
|
.first()
|
||||||
@@ -71,18 +78,20 @@ impl WebSession {
|
|||||||
self.close();
|
self.close();
|
||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Protocol);
|
||||||
}
|
}
|
||||||
if insert_carrier_lane(&mut state, lane_id).is_none() {
|
if state.carrier_lanes.len()
|
||||||
drop(state);
|
>= self.profile.max_streams_per_session.saturating_add(1)
|
||||||
self.close();
|
{
|
||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let lane = state
|
let (last_sequence, last_digest, up_active) = state
|
||||||
.carrier_lanes
|
.carrier_lanes
|
||||||
.get_mut(&lane_id)
|
.get(&lane_id)
|
||||||
.ok_or(ManagerError::Protocol)?;
|
.map_or((0, [0; 32], false), |lane| {
|
||||||
if sequence == lane.last_up_sequence && sequence != 0 {
|
(lane.last_up_sequence, lane.last_up_digest, lane.up_active)
|
||||||
return if bool::from(lane.last_up_digest.ct_eq(&digest)) {
|
});
|
||||||
|
if sequence == last_sequence && sequence != 0 {
|
||||||
|
return if bool::from(last_digest.ct_eq(&digest)) {
|
||||||
Ok(sequence)
|
Ok(sequence)
|
||||||
} else {
|
} else {
|
||||||
drop(state);
|
drop(state);
|
||||||
@@ -90,15 +99,14 @@ impl WebSession {
|
|||||||
Err(ManagerError::Protocol)
|
Err(ManagerError::Protocol)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if sequence == 0 || sequence != lane.last_up_sequence.saturating_add(1) {
|
if sequence == 0 || sequence != last_sequence.saturating_add(1) {
|
||||||
drop(state);
|
drop(state);
|
||||||
self.close();
|
self.close();
|
||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Protocol);
|
||||||
}
|
}
|
||||||
if lane.up_active {
|
if up_active {
|
||||||
return Err(ManagerError::Concurrent);
|
return Err(ManagerError::Concurrent);
|
||||||
}
|
}
|
||||||
lane.up_active = true;
|
|
||||||
if !validate_batch(&state, &frames) {
|
if !validate_batch(&state, &frames) {
|
||||||
drop(state);
|
drop(state);
|
||||||
self.close();
|
self.close();
|
||||||
@@ -116,6 +124,17 @@ impl WebSession {
|
|||||||
}
|
}
|
||||||
return Err(ManagerError::Backpressure);
|
return Err(ManagerError::Backpressure);
|
||||||
}
|
}
|
||||||
|
if new_lane && insert_carrier_lane(&mut state, lane_id).is_none() {
|
||||||
|
self.release_locked(&mut state, reserve_bytes, reserve_items, false);
|
||||||
|
drop(state);
|
||||||
|
self.close();
|
||||||
|
return Err(ManagerError::Protocol);
|
||||||
|
}
|
||||||
|
let Some(lane) = state.carrier_lanes.get_mut(&lane_id) else {
|
||||||
|
self.release_locked(&mut state, reserve_bytes, reserve_items, false);
|
||||||
|
return Err(ManagerError::Closed);
|
||||||
|
};
|
||||||
|
lane.up_active = true;
|
||||||
let mut unused_bytes = reserve_bytes;
|
let mut unused_bytes = reserve_bytes;
|
||||||
let mut unused_items = reserve_items;
|
let mut unused_items = reserve_items;
|
||||||
let applied = self.apply_batch_locked(
|
let applied = self.apply_batch_locked(
|
||||||
@@ -150,6 +169,7 @@ impl WebSession {
|
|||||||
if committed {
|
if committed {
|
||||||
self.finish_carrier_commit();
|
self.finish_carrier_commit();
|
||||||
}
|
}
|
||||||
|
self.lane_open_notify.notify_waiters();
|
||||||
for completion in opened {
|
for completion in opened {
|
||||||
self.spawn_stream(completion, false);
|
self.spawn_stream(completion, false);
|
||||||
}
|
}
|
||||||
@@ -168,14 +188,26 @@ impl WebSession {
|
|||||||
if !self.carrier().uses_lanes() || lane_id > frame::MAX_STREAM_ID {
|
if !self.carrier().uses_lanes() || lane_id > frame::MAX_STREAM_ID {
|
||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Protocol);
|
||||||
}
|
}
|
||||||
let (epoch, notify) = {
|
if !self.wait_for_lane_open(lane_id, cursor).await? {
|
||||||
|
return Ok(PollResult {
|
||||||
|
body: Bytes::new(),
|
||||||
|
next_cursor: cursor,
|
||||||
|
lane_closed: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let (instance, epoch, notify) = {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
if state.closed {
|
if state.closed {
|
||||||
return Err(ManagerError::Closed);
|
return Err(ManagerError::Closed);
|
||||||
}
|
}
|
||||||
|
state.last_activity = Instant::now();
|
||||||
let acknowledged = {
|
let acknowledged = {
|
||||||
let Some(lane) = state.carrier_lanes.get_mut(&lane_id) else {
|
let Some(lane) = state.carrier_lanes.get_mut(&lane_id) else {
|
||||||
return Err(ManagerError::Protocol);
|
return Ok(PollResult {
|
||||||
|
body: Bytes::new(),
|
||||||
|
next_cursor: cursor,
|
||||||
|
lane_closed: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
if let Some(unacked) = &lane.unacked {
|
if let Some(unacked) = &lane.unacked {
|
||||||
if cursor == unacked.base_cursor {
|
if cursor == unacked.base_cursor {
|
||||||
@@ -201,6 +233,14 @@ impl WebSession {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(batch) = acknowledged {
|
if let Some(batch) = acknowledged {
|
||||||
|
if let Some(lane) = state.carrier_lanes.get_mut(&lane_id) {
|
||||||
|
lane.pending_bytes = lane
|
||||||
|
.pending_bytes
|
||||||
|
.saturating_sub(batch.data_bytes.saturating_add(batch.control_bytes));
|
||||||
|
lane.pending_items = lane
|
||||||
|
.pending_items
|
||||||
|
.saturating_sub(batch.data_items.saturating_add(batch.control_items));
|
||||||
|
}
|
||||||
self.release_locked(&mut state, batch.data_bytes, batch.data_items, false);
|
self.release_locked(&mut state, batch.data_bytes, batch.data_items, false);
|
||||||
self.release_locked(&mut state, batch.control_bytes, batch.control_items, true);
|
self.release_locked(&mut state, batch.control_bytes, batch.control_items, true);
|
||||||
if let Some(stream) = state.streams.get_mut(&lane_id)
|
if let Some(stream) = state.streams.get_mut(&lane_id)
|
||||||
@@ -209,13 +249,12 @@ impl WebSession {
|
|||||||
waker.wake();
|
waker.wake();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.last_activity = Instant::now();
|
|
||||||
let lane = state
|
let lane = state
|
||||||
.carrier_lanes
|
.carrier_lanes
|
||||||
.get_mut(&lane_id)
|
.get_mut(&lane_id)
|
||||||
.ok_or(ManagerError::Protocol)?;
|
.ok_or(ManagerError::Protocol)?;
|
||||||
lane.down_epoch = lane.down_epoch.wrapping_add(1).max(1);
|
lane.down_epoch = lane.down_epoch.wrapping_add(1).max(1);
|
||||||
(lane.down_epoch, Arc::clone(&lane.notify))
|
(lane.instance, lane.down_epoch, Arc::clone(&lane.notify))
|
||||||
};
|
};
|
||||||
notify.notify_waiters();
|
notify.notify_waiters();
|
||||||
|
|
||||||
@@ -235,7 +274,7 @@ impl WebSession {
|
|||||||
lane_closed: true,
|
lane_closed: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if lane.down_epoch != epoch {
|
if lane.instance != instance || lane.down_epoch != epoch {
|
||||||
return Ok(PollResult {
|
return Ok(PollResult {
|
||||||
body: Bytes::new(),
|
body: Bytes::new(),
|
||||||
next_cursor: cursor,
|
next_cursor: cursor,
|
||||||
@@ -304,7 +343,7 @@ impl WebSession {
|
|||||||
if state
|
if state
|
||||||
.carrier_lanes
|
.carrier_lanes
|
||||||
.get(&lane_id)
|
.get(&lane_id)
|
||||||
.is_some_and(|lane| lane.down_epoch == epoch)
|
.is_some_and(|lane| lane.instance == instance && lane.down_epoch == epoch)
|
||||||
{
|
{
|
||||||
state.last_activity = Instant::now();
|
state.last_activity = Instant::now();
|
||||||
}
|
}
|
||||||
@@ -317,6 +356,60 @@ impl WebSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn wait_for_lane_open(
|
||||||
|
&self,
|
||||||
|
lane_id: u32,
|
||||||
|
cursor: u64,
|
||||||
|
) -> Result<bool, ManagerError> {
|
||||||
|
let wait = {
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
if state.closed {
|
||||||
|
return Err(ManagerError::Closed);
|
||||||
|
}
|
||||||
|
if state.carrier_lanes.contains_key(&lane_id) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if cursor != 0 || lane_id == 0 {
|
||||||
|
drop(state);
|
||||||
|
self.close();
|
||||||
|
return Err(ManagerError::Protocol);
|
||||||
|
}
|
||||||
|
if state.closed_streams.contains(&lane_id)
|
||||||
|
|| state.closing_streams.contains_key(&lane_id)
|
||||||
|
{
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if state.lane_open_waits >= self.limits.max_lane_open_waits_per_session {
|
||||||
|
return Err(ManagerError::Limit);
|
||||||
|
}
|
||||||
|
state.lane_open_waits += 1;
|
||||||
|
LaneOpenWaitGuard { session: self }
|
||||||
|
};
|
||||||
|
let deadline = Duration::from_secs(self.timeouts.lane_open_wait_secs);
|
||||||
|
let opened = tokio::time::timeout(deadline, async {
|
||||||
|
loop {
|
||||||
|
let notified = self.lane_open_notify.notified();
|
||||||
|
{
|
||||||
|
let state = self.state.lock();
|
||||||
|
if state.closed
|
||||||
|
|| state.carrier_lanes.contains_key(&lane_id)
|
||||||
|
|| state.closed_streams.contains(&lane_id)
|
||||||
|
|| state.closing_streams.contains_key(&lane_id)
|
||||||
|
{
|
||||||
|
return state.carrier_lanes.contains_key(&lane_id)
|
||||||
|
|| state.closed_streams.contains(&lane_id)
|
||||||
|
|| state.closing_streams.contains_key(&lane_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notified.await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_or(false);
|
||||||
|
drop(wait);
|
||||||
|
Ok(opened)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn queue_lane_frame_locked(
|
pub(super) fn queue_lane_frame_locked(
|
||||||
&self,
|
&self,
|
||||||
state: &mut SessionState,
|
state: &mut SessionState,
|
||||||
@@ -363,6 +456,15 @@ impl WebSession {
|
|||||||
<= self.limits.max_frame_payload_bytes
|
<= self.limits.max_frame_payload_bytes
|
||||||
});
|
});
|
||||||
if can_coalesce {
|
if can_coalesce {
|
||||||
|
if state.carrier_lanes.get(&stream_id).is_none_or(|lane| {
|
||||||
|
lane.pending_bytes
|
||||||
|
> self
|
||||||
|
.limits
|
||||||
|
.pending_bytes_per_lane
|
||||||
|
.saturating_sub(payload.len())
|
||||||
|
}) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if !self.reserve_locked(state, payload.len(), 0, PendingClass::Downlink) {
|
if !self.reserve_locked(state, payload.len(), 0, PendingClass::Downlink) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -378,6 +480,7 @@ impl WebSession {
|
|||||||
last.cost += payload.len();
|
last.cost += payload.len();
|
||||||
let payload_len = (last.encoded.len() - frame::HEADER_BYTES) as u32;
|
let payload_len = (last.encoded.len() - frame::HEADER_BYTES) as u32;
|
||||||
last.encoded[4..8].copy_from_slice(&payload_len.to_be_bytes());
|
last.encoded[4..8].copy_from_slice(&payload_len.to_be_bytes());
|
||||||
|
lane.pending_bytes += payload.len();
|
||||||
lane.notify.notify_waiters();
|
lane.notify.notify_waiters();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -387,6 +490,14 @@ impl WebSession {
|
|||||||
} else {
|
} else {
|
||||||
PendingClass::Downlink
|
PendingClass::Downlink
|
||||||
};
|
};
|
||||||
|
if state.carrier_lanes.get(&stream_id).is_none_or(|lane| {
|
||||||
|
lane.pending_bytes
|
||||||
|
> self.limits.pending_bytes_per_lane.saturating_sub(cost)
|
||||||
|
|| lane.pending_items
|
||||||
|
>= self.limits.pending_items_per_lane
|
||||||
|
}) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if !self.reserve_locked(state, cost, 1, class) {
|
if !self.reserve_locked(state, cost, 1, class) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -409,6 +520,8 @@ impl WebSession {
|
|||||||
control,
|
control,
|
||||||
cost,
|
cost,
|
||||||
});
|
});
|
||||||
|
lane.pending_bytes += cost;
|
||||||
|
lane.pending_items += 1;
|
||||||
if frame_type == FrameType::Window {
|
if frame_type == FrameType::Window {
|
||||||
lane.pending_windows.insert(stream_id, index);
|
lane.pending_windows.insert(stream_id, index);
|
||||||
}
|
}
|
||||||
@@ -455,6 +568,18 @@ impl WebSession {
|
|||||||
}
|
}
|
||||||
self.release_locked(state, data_bytes, data_items, false);
|
self.release_locked(state, data_bytes, data_items, false);
|
||||||
self.release_locked(state, control_bytes, control_items, true);
|
self.release_locked(state, control_bytes, control_items, true);
|
||||||
|
self.lane_open_notify.notify_waiters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LaneOpenWaitGuard<'a> {
|
||||||
|
session: &'a WebSession,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LaneOpenWaitGuard<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut state = self.session.state.lock();
|
||||||
|
state.lane_open_waits = state.lane_open_waits.saturating_sub(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ impl WebSession {
|
|||||||
);
|
);
|
||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Protocol);
|
||||||
}
|
}
|
||||||
|
drop(state);
|
||||||
|
self.lane_open_notify.notify_waiters();
|
||||||
Ok(WebSocketLaneReservation {
|
Ok(WebSocketLaneReservation {
|
||||||
session: Arc::clone(self),
|
session: Arc::clone(self),
|
||||||
lane_id,
|
lane_id,
|
||||||
@@ -229,6 +231,7 @@ impl WebSession {
|
|||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
let reserved = state.websocket_lane_reservations.remove(&lane_id);
|
let reserved = state.websocket_lane_reservations.remove(&lane_id);
|
||||||
if let Some(stream) = state.streams.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);
|
let (bytes, items) = inbound_queue_cost(&stream.inbound);
|
||||||
self.release_locked(&mut state, bytes, items, false);
|
self.release_locked(&mut state, bytes, items, false);
|
||||||
if let Some(waker) = stream.read_waker {
|
if let Some(waker) = stream.read_waker {
|
||||||
@@ -245,6 +248,7 @@ impl WebSession {
|
|||||||
if let Some(peer_port) = reserved {
|
if let Some(peer_port) = reserved {
|
||||||
self.release_websocket_lane_reservation(lane_id, peer_port);
|
self.release_websocket_lane_reservation(lane_id, peer_port);
|
||||||
}
|
}
|
||||||
|
self.lane_open_notify.notify_waiters();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release_websocket_lane_reservation(&self, lane_id: u32, peer_port: u16) {
|
fn release_websocket_lane_reservation(&self, lane_id: u32, peer_port: u16) {
|
||||||
|
|||||||
Reference in New Issue
Block a user