This commit is contained in:
Alexey
2026-08-27 00:06:37 +03:00
parent 1b3477a333
commit 14e8d10ad3
34 changed files with 221 additions and 299 deletions
+1 -4
View File
@@ -85,10 +85,7 @@ pub(super) fn push_body(
html.push_str("</pre>"); html.push_str("</pre>");
} }
pub(super) fn push_lifecycle( pub(super) fn push_lifecycle(html: &mut String, event: &crate::web::trace::TraceLifecycleRecord) {
html: &mut String,
event: &crate::web::trace::TraceLifecycleRecord,
) {
html.push_str("<pre>event: "); html.push_str("<pre>event: ");
html.push_str(event.event.as_str()); html.push_str(event.event.as_str());
html.push_str("\nstream: "); html.push_str("\nstream: ");
+1 -3
View File
@@ -43,9 +43,7 @@ pub(super) fn validate(timeouts: &WebTimeoutsConfig) -> Result<()> {
return config_error("web.timeouts.websocket_open_secs must be within [1, 300]"); return config_error("web.timeouts.websocket_open_secs must be within [1, 300]");
} }
if timeouts.lane_open_wait_secs > timeouts.long_poll_secs { if timeouts.lane_open_wait_secs > timeouts.long_poll_secs {
return config_error( return config_error("web.timeouts.lane_open_wait_secs must not exceed long_poll_secs");
"web.timeouts.lane_open_wait_secs must not exceed long_poll_secs",
);
} }
if timeouts.carrier_health_secs > timeouts.reconnect_grace_secs { if timeouts.carrier_health_secs > timeouts.reconnect_grace_secs {
return config_error( return config_error(
@@ -50,7 +50,10 @@ fn web_config_builds_canonical_runtime_snapshot() {
assert_eq!(vhost.profiles[0].key_fingerprint.len(), 16); assert_eq!(vhost.profiles[0].key_fingerprint.len(), 16);
assert_ne!(vhost.profiles[0].key_fingerprint, "0001020304050607"); assert_ne!(vhost.profiles[0].key_fingerprint, "0001020304050607");
assert!(!vhost.profiles[0].carrier_negotiation_enabled); assert!(!vhost.profiles[0].carrier_negotiation_enabled);
assert_eq!(vhost.profiles[0].carriers.as_ref(), [WebCarrier::HttpsLanes]); assert_eq!(
vhost.profiles[0].carriers.as_ref(),
[WebCarrier::HttpsLanes]
);
} }
#[test] #[test]
@@ -95,11 +98,7 @@ fn web_carrier_array_enables_ordered_negotiation_and_appends_fallback() {
#[test] #[test]
fn web_carriers_reject_true_empty_and_duplicates() { fn web_carriers_reject_true_empty_and_duplicates() {
for value in [ for value in ["true", "[]", "[\"https\", \"https\"]"] {
"true",
"[]",
"[\"https\", \"https\"]",
] {
let invalid = WEB_CONFIG.replace( let invalid = WEB_CONFIG.replace(
"carrier = \"https-lanes\"", "carrier = \"https-lanes\"",
&format!("carrier = \"https-lanes\"\ncarriers = {value}"), &format!("carrier = \"https-lanes\"\ncarriers = {value}"),
+2 -2
View File
@@ -56,12 +56,12 @@ pub use web::{
WebCarrierNegotiationAggressiveness, WebConfig, WebDecoyConfig, WebLimitsConfig, WebCarrierNegotiationAggressiveness, WebConfig, WebDecoyConfig, WebLimitsConfig,
WebProfileConfig, WebSecretMode, WebTimeoutsConfig, WebVhostConfig, WebProfileConfig, WebSecretMode, WebTimeoutsConfig, WebVhostConfig,
}; };
#[allow(unused_imports)]
pub use web_carrier::{WebCarrier, WebCarriers};
pub(crate) use web::{ pub(crate) use web::{
WebRuntimeConfig, WebRuntimeDecoy, WebRuntimeProfile, WebRuntimeVhost, WebStaticAsset, WebRuntimeConfig, WebRuntimeDecoy, WebRuntimeProfile, WebRuntimeVhost, WebStaticAsset,
WebStaticSite, WebStaticSite,
}; };
#[allow(unused_imports)]
pub use web_carrier::{WebCarrier, WebCarriers};
pub(crate) use web_debug::web_debug_fits_limits; pub(crate) use web_debug::web_debug_fits_limits;
pub use web_debug::{WebDebugBodyCapture, WebDebugConfig}; pub use web_debug::{WebDebugBodyCapture, WebDebugConfig};
+3 -6
View File
@@ -234,8 +234,7 @@ impl Default for WebLimitsConfig {
websocket_admission_watermark_pct: default_web_websocket_admission_watermark_pct(), websocket_admission_watermark_pct: default_web_websocket_admission_watermark_pct(),
websocket_eviction_watermark_pct: default_web_websocket_eviction_watermark_pct(), websocket_eviction_watermark_pct: default_web_websocket_eviction_watermark_pct(),
websocket_http_connection_reserve: default_web_websocket_http_connection_reserve(), websocket_http_connection_reserve: default_web_websocket_http_connection_reserve(),
max_websocket_evictions_in_flight: max_websocket_evictions_in_flight: default_web_max_websocket_evictions_in_flight(),
default_web_max_websocket_evictions_in_flight(),
max_carrier_learning_entries: default_web_max_carrier_learning_entries(), max_carrier_learning_entries: default_web_max_carrier_learning_entries(),
max_body_readers: default_web_max_body_readers(), max_body_readers: default_web_max_body_readers(),
max_body_bytes_global: default_web_max_body_bytes_global(), max_body_bytes_global: default_web_max_body_bytes_global(),
@@ -348,8 +347,7 @@ impl Default for WebTimeoutsConfig {
websocket_write_secs: default_web_websocket_write_secs(), websocket_write_secs: default_web_websocket_write_secs(),
websocket_backpressure_secs: default_web_websocket_backpressure_secs(), websocket_backpressure_secs: default_web_websocket_backpressure_secs(),
websocket_eviction_secs: default_web_websocket_eviction_secs(), websocket_eviction_secs: default_web_websocket_eviction_secs(),
carrier_negotiation_deadlines_secs: carrier_negotiation_deadlines_secs: default_web_carrier_negotiation_deadlines_secs(),
default_web_carrier_negotiation_deadlines_secs(),
carrier_learning_secs: default_web_carrier_learning_secs(), carrier_learning_secs: default_web_carrier_learning_secs(),
bootstrap_lifetime_secs: default_web_bootstrap_lifetime_secs(), bootstrap_lifetime_secs: default_web_bootstrap_lifetime_secs(),
reconnect_grace_secs: default_web_reconnect_grace_secs(), reconnect_grace_secs: default_web_reconnect_grace_secs(),
@@ -436,8 +434,7 @@ impl Default for WebConfig {
carrier: WebCarrier::default(), carrier: WebCarrier::default(),
carriers: WebCarriers::default(), carriers: WebCarriers::default(),
carrier_learning: default_web_carrier_learning(), carrier_learning: default_web_carrier_learning(),
carrier_negotiation_aggressiveness: carrier_negotiation_aggressiveness: WebCarrierNegotiationAggressiveness::default(),
WebCarrierNegotiationAggressiveness::default(),
limits: WebLimitsConfig::default(), limits: WebLimitsConfig::default(),
debug: WebDebugConfig::default(), debug: WebDebugConfig::default(),
timeouts: WebTimeoutsConfig::default(), timeouts: WebTimeoutsConfig::default(),
+26 -22
View File
@@ -16,10 +16,7 @@ fn render_page(bootstrap: &str, candidate_count: usize) -> BridgePage {
#[test] #[test]
fn rendered_page_contains_bounded_negotiation_contract() { fn rendered_page_contains_bounded_negotiation_contract() {
let page = render_page( let page = render_page("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 4);
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
4,
);
assert!(!page.body.contains("__")); assert!(!page.body.contains("__"));
assert!(!page.body.contains("bridge=")); assert!(!page.body.contains("bridge="));
assert!(page.body.contains("X-Carrier-Capabilities")); assert!(page.body.contains("X-Carrier-Capabilities"));
@@ -40,17 +37,23 @@ fn rendered_page_contains_bounded_negotiation_contract() {
fn rendered_page_preserves_the_ios_bootstrap_literal() { fn rendered_page_preserves_the_ios_bootstrap_literal() {
let bootstrap = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; let bootstrap = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
let page = render_page(bootstrap, 2); let page = render_page(bootstrap, 2);
assert!(page.body.contains(&format!("const bootstrap=\"{bootstrap}\""))); assert!(
page.body
.contains(&format!("const bootstrap=\"{bootstrap}\""))
);
} }
#[test] #[test]
fn effective_deadline_formula_uses_the_final_checkpoint() { fn effective_deadline_formula_uses_the_final_checkpoint() {
let page = render_page( let page = render_page("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", 3);
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", assert!(
3, page.body
.contains("negotiatedFinalDeadline=candidateDeadlines[3]")
);
assert!(
page.body
.contains("carrierAttempt>=negotiatedCandidateCount?negotiatedFinalDeadline")
); );
assert!(page.body.contains("negotiatedFinalDeadline=candidateDeadlines[3]"));
assert!(page.body.contains("carrierAttempt>=negotiatedCandidateCount?negotiatedFinalDeadline"));
} }
#[test] #[test]
@@ -69,18 +72,19 @@ fn disabled_negotiation_does_not_arm_a_carrier_deadline() {
assert!(page.body.contains( assert!(page.body.contains(
"if(negotiationEnabled){negotiationStartedAt=Date.now();armCarrierDeadline(attemptEpoch)}" "if(negotiationEnabled){negotiationStartedAt=Date.now();armCarrierDeadline(attemptEpoch)}"
)); ));
assert!(page.body.contains( assert!(
"negotiationEnabled?'tproxy-auto-v1.':'tproxy-v1.'" page.body
)); .contains("negotiationEnabled?'tproxy-auto-v1.':'tproxy-v1.'")
);
} }
#[test] #[test]
fn retry_and_attempt_state_are_frozen_before_fetch() { fn retry_and_attempt_state_are_frozen_before_fetch() {
let page = render_page( let page = render_page("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", 4);
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", assert!(
4, page.body
.contains("async function request(path,frozenOptions)")
); );
assert!(page.body.contains("async function request(path,frozenOptions)"));
assert!(!page.body.contains("makeOptions")); assert!(!page.body.contains("makeOptions"));
assert!( assert!(
page.body page.body
@@ -93,14 +97,14 @@ fn retry_and_attempt_state_are_frozen_before_fetch() {
#[test] #[test]
fn ambiguous_commit_is_resolved_before_carrier_advance() { fn ambiguous_commit_is_resolved_before_carrier_advance() {
let page = render_page( let page = render_page("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 4);
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
4,
);
assert!(page.body.contains("resolveAttempt(reason,epoch,snapshot)")); assert!(page.body.contains("resolveAttempt(reason,epoch,snapshot)"));
assert!(page.body.contains( assert!(page.body.contains(
"sessionEcho(response,snapshot.attempt,['provisional','committed','healthy'],true)" "sessionEcho(response,snapshot.attempt,['provisional','committed','healthy'],true)"
)); ));
assert!(page.body.contains("if(echo.state!=='provisional'){switching=false;fail();return}")); assert!(
page.body
.contains("if(echo.state!=='provisional'){switching=false;fail();return}")
);
assert!(page.body.contains("const token=cleanupToken||sessionToken")); assert!(page.body.contains("const token=cleanupToken||sessionToken"));
} }
+1 -5
View File
@@ -356,11 +356,7 @@ async fn handle_up(
} }
}; };
if let Some(trace) = request_trace(&request) { if let Some(trace) = request_trace(&request) {
trace.record_frames( trace.record_frames(TraceDirection::Request, &body, session.limits());
TraceDirection::Request,
&body,
session.limits(),
);
} }
let result = match lane_id { let result = match lane_id {
Some(lane_id) => session.process_up_lane(lane_id, sequence, &body), Some(lane_id) => session.process_up_lane(lane_id, sequence, &body),
+1 -5
View File
@@ -89,11 +89,7 @@ pub(super) async fn handle_down(
} }
Ok(result) => { Ok(result) => {
if let Some(trace) = request_trace(&request) { if let Some(trace) = request_trace(&request) {
trace.record_frames( trace.record_frames(TraceDirection::Response, &result.body, session.limits());
TraceDirection::Response,
&result.body,
session.limits(),
);
} }
let mut response = full_response(StatusCode::OK, result.body); let mut response = full_response(StatusCode::OK, result.body);
carrier_headers(&mut response); carrier_headers(&mut response);
+34 -35
View File
@@ -130,13 +130,7 @@ async fn metadata_free_native_client_can_use_each_fixed_carrier() {
let response = request( let response = request(
&listener, &listener,
&runtime, &runtime,
create_request_with_headers( create_request_with_headers(&bootstrap, &hello, None, None, NATIVE_USER_AGENT_HEADER),
&bootstrap,
&hello,
None,
None,
NATIVE_USER_AGENT_HEADER,
),
) )
.await; .await;
let (headers, _) = split_response(&response); let (headers, _) = split_response(&response);
@@ -170,13 +164,7 @@ async fn metadata_free_native_client_uses_fallback_when_candidates_are_enabled()
let response = request( let response = request(
&listener, &listener,
&runtime, &runtime,
create_request_with_headers( create_request_with_headers(&bootstrap, &hello, None, None, NATIVE_USER_AGENT_HEADER),
&bootstrap,
&hello,
None,
None,
NATIVE_USER_AGENT_HEADER,
),
) )
.await; .await;
let (headers, _) = split_response(&response); let (headers, _) = split_response(&response);
@@ -209,13 +197,7 @@ async fn explicit_native_capabilities_participate_in_automatic_selection() {
let response = request( let response = request(
&listener, &listener,
&runtime, &runtime,
create_request_with_headers( create_request_with_headers(&bootstrap, &hello, Some(1), None, NATIVE_USER_AGENT_HEADER),
&bootstrap,
&hello,
Some(1),
None,
NATIVE_USER_AGENT_HEADER,
),
) )
.await; .await;
let (headers, _) = split_response(&response); let (headers, _) = split_response(&response);
@@ -261,9 +243,18 @@ async fn negotiation_replays_replaces_and_freezes_after_carrier_commit() {
let replay = request(&listener, &runtime, first_request).await; let replay = request(&listener, &runtime, first_request).await;
let (replay_headers, _) = split_response(&replay); let (replay_headers, _) = split_response(&replay);
assert_eq!(response_header(replay_headers, "x-session-token"), first_token); assert_eq!(
assert_eq!(response_header(replay_headers, "x-carrier-candidate-count"), "3"); response_header(replay_headers, "x-session-token"),
assert_eq!(response_header(replay_headers, "x-carrier-state"), "provisional"); first_token
);
assert_eq!(
response_header(replay_headers, "x-carrier-candidate-count"),
"3"
);
assert_eq!(
response_header(replay_headers, "x-carrier-state"),
"provisional"
);
let second_request = create_request(&bootstrap, &hello, Some(2), Some("timeout")); let second_request = create_request(&bootstrap, &hello, Some(2), Some("timeout"));
let second = request(&listener, &runtime, second_request.clone()).await; let second = request(&listener, &runtime, second_request.clone()).await;
@@ -346,11 +337,20 @@ async fn negotiation_replays_replaces_and_freezes_after_carrier_commit() {
let (third_headers, _) = split_response(&third); let (third_headers, _) = split_response(&third);
assert!(third_headers.starts_with(b"HTTP/1.1 409")); assert!(third_headers.starts_with(b"HTTP/1.1 409"));
assert!(optional_response_header(third_headers, "x-session-token").is_none()); assert!(optional_response_header(third_headers, "x-session-token").is_none());
assert_eq!(response_header(third_headers, "x-carrier-mode"), "https-lanes"); assert_eq!(
response_header(third_headers, "x-carrier-mode"),
"https-lanes"
);
assert_eq!(response_header(third_headers, "x-carrier-attempt"), "2"); assert_eq!(response_header(third_headers, "x-carrier-attempt"), "2");
assert_eq!(response_header(third_headers, "x-carrier-candidate-count"), "3"); assert_eq!(
response_header(third_headers, "x-carrier-candidate-count"),
"3"
);
assert_eq!(response_header(third_headers, "x-carrier-deadline"), "12"); assert_eq!(response_header(third_headers, "x-carrier-deadline"), "12");
assert_eq!(response_header(third_headers, "x-carrier-state"), "committed"); assert_eq!(
response_header(third_headers, "x-carrier-state"),
"committed"
);
assert!( assert!(
runtime runtime
.get_session(token_hash(&second_token), "proxy.example.com") .get_session(token_hash(&second_token), "proxy.example.com")
@@ -397,7 +397,10 @@ async fn timed_out_attempt_replays_before_successor_own_deadline() {
response_header(replay_headers, "x-session-token"), response_header(replay_headers, "x-session-token"),
first_token first_token
); );
assert_eq!(response_header(replay_headers, "x-carrier-state"), "provisional"); assert_eq!(
response_header(replay_headers, "x-carrier-state"),
"provisional"
);
let second = request( let second = request(
&listener, &listener,
@@ -453,9 +456,8 @@ async fn https_lane_downlink_can_arrive_before_its_uplink_open() {
.into_bytes(); .into_bytes();
let down_listener = Arc::clone(&listener); let down_listener = Arc::clone(&listener);
let down_runtime = Arc::clone(&runtime); let down_runtime = Arc::clone(&runtime);
let down = tokio::spawn(async move { let down =
request(&down_listener, &down_runtime, down_request).await tokio::spawn(async move { request(&down_listener, &down_runtime, down_request).await });
});
tokio::task::yield_now().await; tokio::task::yield_now().await;
let open = frame::encode(FrameType::Open, 7, &[]); let open = frame::encode(FrameType::Open, 7, &[]);
@@ -477,10 +479,7 @@ async fn https_lane_downlink_can_arrive_before_its_uplink_open() {
.unwrap() .unwrap()
.unwrap(); .unwrap();
let (down_headers, _) = split_response(&down); let (down_headers, _) = split_response(&down);
assert!( assert!(down_headers.starts_with(b"HTTP/1.1 200") || down_headers.starts_with(b"HTTP/1.1 204"));
down_headers.starts_with(b"HTTP/1.1 200")
|| down_headers.starts_with(b"HTTP/1.1 204")
);
assert!(optional_response_header(down_headers, "x-down-cursor").is_some()); assert!(optional_response_header(down_headers, "x-down-cursor").is_some());
runtime.shutdown().await; runtime.shutdown().await;
+8 -15
View File
@@ -15,9 +15,7 @@ const USER_AGENT_CONTEXT: &[u8] = b"telemt-web-carrier-user-agent-v1\0";
// Canonical host and forwarded-address provenance remain isolated from credentials. // Canonical host and forwarded-address provenance remain isolated from credentials.
mod identity; mod identity;
pub(super) use identity::{ pub(super) use identity::{canonical_request_host, carrier_ip_learning_eligible, client_ip};
canonical_request_host, carrier_ip_learning_eligible, client_ip,
};
/// Decodes an exact canonical bridge query without allocating credential strings. /// Decodes an exact canonical bridge query without allocating credential strings.
pub(super) fn bridge_candidate(query: Option<&str>) -> ([u8; 32], bool) { pub(super) fn bridge_candidate(query: Option<&str>) -> ([u8; 32], bool) {
@@ -205,17 +203,13 @@ fn parse_capabilities(value: &str) -> Option<CarrierCapabilities> {
} }
fn strict_browser_hint<B>(request: &Request<B>, host: &str) -> bool { fn strict_browser_hint<B>(request: &Request<B>, host: &str) -> bool {
single_header(request, header::ORIGIN) single_header(request, header::ORIGIN).is_some_and(|value| value == format!("https://{host}"))
.is_some_and(|value| value == format!("https://{host}"))
&& single_header(request, "sec-fetch-site") == Some("same-origin") && single_header(request, "sec-fetch-site") == Some("same-origin")
&& single_header(request, "sec-fetch-mode") == Some("cors") && single_header(request, "sec-fetch-mode") == Some("cors")
&& single_header(request, "sec-fetch-dest") == Some("empty") && single_header(request, "sec-fetch-dest") == Some("empty")
} }
fn optional_canonical_u8_header<B>( fn optional_canonical_u8_header<B>(request: &Request<B>, name: &'static str) -> Option<Option<u8>> {
request: &Request<B>,
name: &'static str,
) -> Option<Option<u8>> {
if !request.headers().contains_key(name) { if !request.headers().contains_key(name) {
return Some(None); return Some(None);
} }
@@ -452,9 +446,11 @@ mod tests {
.header(header::USER_AGENT, "Native") .header(header::USER_AGENT, "Native")
.body(()) .body(())
.unwrap(); .unwrap();
assert!(!carrier_request(&legacy, "proxy.example.com") assert!(
!carrier_request(&legacy, "proxy.example.com")
.unwrap() .unwrap()
.is_automatic()); .is_automatic()
);
let reordered = Request::builder() let reordered = Request::builder()
.header("x-carrier-capabilities", "websocket,https") .header("x-carrier-capabilities", "websocket,https")
@@ -479,10 +475,7 @@ mod tests {
assert!(!parsed.uses_capabilities()); assert!(!parsed.uses_capabilities());
let automatic = Request::builder() let automatic = Request::builder()
.header( .header("x-carrier-capabilities", "https,https-lanes")
"x-carrier-capabilities",
"https,https-lanes",
)
.header("x-carrier-attempt", "1") .header("x-carrier-attempt", "1")
.header( .header(
header::USER_AGENT, header::USER_AGENT,
+3 -6
View File
@@ -152,12 +152,9 @@ pub(super) async fn handle_session(
} }
Err(ManagerError::Committed) => { Err(ManagerError::Committed) => {
let mut response = carrier_empty(StatusCode::CONFLICT); let mut response = carrier_empty(StatusCode::CONFLICT);
if let Some(echo) = runtime.carrier_echo( if let Some(echo) =
token_hash, runtime.carrier_echo(token_hash, &vhost.host, client_ip, carrier_request)
&vhost.host, {
client_ip,
carrier_request,
) {
response.headers_mut().insert( response.headers_mut().insert(
HeaderName::from_static("x-carrier-mode"), HeaderName::from_static("x-carrier-mode"),
HeaderValue::from_static(echo.carrier.as_str()), HeaderValue::from_static(echo.carrier.as_str()),
+5 -4
View File
@@ -134,8 +134,7 @@ fn runtime_config_with_carriers_and_deadlines(
WebCarriers::Disabled WebCarriers::Disabled
}; };
config.web.carrier_learning = carrier_learning; config.web.carrier_learning = carrier_learning;
config.web.timeouts.carrier_negotiation_deadlines_secs = config.web.timeouts.carrier_negotiation_deadlines_secs = carrier_negotiation_deadlines_secs;
carrier_negotiation_deadlines_secs;
config.web.limits.max_bootstraps_per_ip = 1; config.web.limits.max_bootstraps_per_ip = 1;
config.web.timeouts.shutdown_secs = 1; config.web.timeouts.shutdown_secs = 1;
config.web.runtime = Some(Arc::new(WebRuntimeConfig { config.web.runtime = Some(Arc::new(WebRuntimeConfig {
@@ -260,9 +259,11 @@ async fn https_carrier_bootstraps_and_closes_one_session() {
.windows(11) .windows(11)
.any(|value| value == b"bootstrap=\"") .any(|value| value == b"bootstrap=\"")
); );
assert!(next_root_body assert!(
next_root_body
.windows(b"const negotiationEnabled=false".len()) .windows(b"const negotiationEnabled=false".len())
.any(|value| value == b"const negotiationEnabled=false")); .any(|value| value == b"const negotiationEnabled=false")
);
let close = format!( let close = format!(
"DELETE /api/v1/session HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nAuthorization: Bearer {session}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" "DELETE /api/v1/session HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nAuthorization: Bearer {session}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
+3 -7
View File
@@ -389,9 +389,8 @@ fn parse_upgrade<B>(request: &Request<B>) -> Option<ParsedUpgrade> {
{ {
return None; return None;
} }
let (token, carrier, acknowledge_commit) = if let Some(token) = let (token, carrier, acknowledge_commit) =
protocol.strip_prefix("tproxy-auto-v1.") if let Some(token) = protocol.strip_prefix("tproxy-auto-v1.") {
{
(token, ParsedCarrier::Multiplex, true) (token, ParsedCarrier::Multiplex, true)
} else if let Some(lane) = protocol.strip_prefix("tproxy-auto-lane-v1.") { } else if let Some(lane) = protocol.strip_prefix("tproxy-auto-lane-v1.") {
let (token, lane_id) = parse_lane_protocol(lane)?; let (token, lane_id) = parse_lane_protocol(lane)?;
@@ -441,10 +440,7 @@ fn parse_lane_protocol(value: &str) -> Option<(&str, u32)> {
Some((token, lane_id)) Some((token, lane_id))
} }
fn single_header<B>( fn single_header<B>(request: &Request<B>, name: impl hyper::header::AsHeaderName) -> Option<&str> {
request: &Request<B>,
name: impl hyper::header::AsHeaderName,
) -> Option<&str> {
let mut values = request.headers().get_all(name).iter(); let mut values = request.headers().get_all(name).iter();
let value = values.next()?.to_str().ok()?; let value = values.next()?.to_str().ok()?;
values.next().is_none().then_some(value) values.next().is_none().then_some(value)
+4 -15
View File
@@ -8,12 +8,8 @@ use tokio_tungstenite::tungstenite::protocol::{Message, Role, WebSocketConfig};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use super::ConnectionIo; use super::ConnectionIo;
use crate::web::manager::{ use crate::web::manager::{WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection};
WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection, use crate::web::session::{WebSession, WebSocketLaneReservation, WebSocketProbeReservation};
};
use crate::web::session::{
WebSession, WebSocketLaneReservation, WebSocketProbeReservation,
};
use crate::web::trace::{TraceDirection, TraceWebSocketContext}; use crate::web::trace::{TraceDirection, TraceWebSocketContext};
const READ_BUFFER_BYTES: usize = 64 * 1024; const READ_BUFFER_BYTES: usize = 64 * 1024;
@@ -126,8 +122,7 @@ async fn run_multiplex(
let mut next_ping = Instant::now() + liveness_interval; let mut next_ping = Instant::now() + liveness_interval;
let open_deadline = let open_deadline =
Instant::now() + Duration::from_secs(session.timeouts().websocket_open_secs); Instant::now() + Duration::from_secs(session.timeouts().websocket_open_secs);
let backpressure_timeout = let backpressure_timeout = Duration::from_secs(session.timeouts().websocket_backpressure_secs);
Duration::from_secs(session.timeouts().websocket_backpressure_secs);
let write_timeout = Duration::from_secs(session.timeouts().websocket_write_secs); let write_timeout = Duration::from_secs(session.timeouts().websocket_write_secs);
let maximum_message = session.limits().carrier_batch_bytes; let maximum_message = session.limits().carrier_batch_bytes;
let mut active = false; let mut active = false;
@@ -317,13 +312,7 @@ async fn run_multiplex(
started, started,
); );
} else { } else {
send( send(socket, Message::Binary(body), &cancellation, write_timeout).await?;
socket,
Message::Binary(body),
&cancellation,
write_timeout,
)
.await?;
} }
connection.mark_progress(); connection.mark_progress();
} }
+2 -10
View File
@@ -24,16 +24,8 @@ pub(super) async fn read_message(
ready = socket.get_ref().readable() => ready.map_err(|_| ())?, ready = socket.get_ref().readable() => ready.map_err(|_| ())?,
} }
if retained_budget.is_none() { if retained_budget.is_none() {
*retained_budget = Some( *retained_budget =
reserve_data( Some(reserve_data(runtime, owner, maximum, cancellation, backpressure_timeout).await?);
runtime,
owner,
maximum,
cancellation,
backpressure_timeout,
)
.await?,
);
} }
let message = tokio::select! { let message = tokio::select! {
_ = cancellation.cancelled() => return Err(()), _ = cancellation.cancelled() => return Err(()),
+3 -12
View File
@@ -7,9 +7,7 @@ use tokio_util::sync::CancellationToken;
use super::CarrierSocket; use super::CarrierSocket;
use super::io::{flush, process_lane, read_message, record_message, reserve_data, send}; use super::io::{flush, process_lane, read_message, record_message, reserve_data, send};
use crate::web::manager::{ use crate::web::manager::{WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection};
WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection,
};
use crate::web::session::{WebSession, WebSocketLaneReservation}; use crate::web::session::{WebSession, WebSocketLaneReservation};
use crate::web::trace::{TraceDirection, TraceWebSocketContext}; use crate::web::trace::{TraceDirection, TraceWebSocketContext};
@@ -32,8 +30,7 @@ pub(super) async fn run_lane(
let mut next_ping = Instant::now() + liveness_interval; let mut next_ping = Instant::now() + liveness_interval;
let open_deadline = let open_deadline =
Instant::now() + Duration::from_secs(session.timeouts().websocket_open_secs); Instant::now() + Duration::from_secs(session.timeouts().websocket_open_secs);
let backpressure_timeout = let backpressure_timeout = Duration::from_secs(session.timeouts().websocket_backpressure_secs);
Duration::from_secs(session.timeouts().websocket_backpressure_secs);
let write_timeout = Duration::from_secs(session.timeouts().websocket_write_secs); let write_timeout = Duration::from_secs(session.timeouts().websocket_write_secs);
let maximum_message = session.limits().carrier_batch_bytes; let maximum_message = session.limits().carrier_batch_bytes;
let mut active = false; let mut active = false;
@@ -232,13 +229,7 @@ pub(super) async fn run_lane(
started, started,
); );
} else { } else {
send( send(socket, Message::Binary(body), &cancellation, write_timeout).await?;
socket,
Message::Binary(body),
&cancellation,
write_timeout,
)
.await?;
} }
connection.mark_progress(); connection.mark_progress();
} }
+1 -2
View File
@@ -452,8 +452,7 @@ async fn failed_automatic_multiplex_socket_remains_supersedable() {
Arc::from([WebCarrier::Websocket, WebCarrier::Https]), Arc::from([WebCarrier::Websocket, WebCarrier::Https]),
); );
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let (bootstrap_hash, hello, session, session_hash) = let (bootstrap_hash, hello, session, session_hash) = create_automatic_session(&live.runtime);
create_automatic_session(&live.runtime);
let protocol = format!("tproxy-auto-v1.{session}"); let protocol = format!("tproxy-auto-v1.{session}");
let mut socket = upgrade(&listener, &live.runtime, &protocol).await; let mut socket = upgrade(&listener, &live.runtime, &protocol).await;
socket.close(None).await.unwrap(); socket.close(None).await.unwrap();
+2 -5
View File
@@ -39,10 +39,10 @@ mod budget;
mod websocket; mod websocket;
pub(crate) use budget::WebSocketBudgetLease; pub(crate) use budget::WebSocketBudgetLease;
use budget::{WebDataBudget, WebSocketBudgetClass}; use budget::{WebDataBudget, WebSocketBudgetClass};
use state::{ManagerState, StreamAdmissionState};
pub(crate) use negotiation::{ pub(crate) use negotiation::{
CarrierCapabilities, CarrierClientClass, CarrierFailure, CarrierLearningContext, CarrierRequest, CarrierCapabilities, CarrierClientClass, CarrierFailure, CarrierLearningContext, CarrierRequest,
}; };
use state::{ManagerState, StreamAdmissionState};
pub(crate) use websocket::{WebSocketConnection, WebSocketKind}; pub(crate) use websocket::{WebSocketConnection, WebSocketKind};
const TOKEN_BYTES: usize = 32; const TOKEN_BYTES: usize = 32;
@@ -327,10 +327,7 @@ impl WebProcessRuntime {
} }
/// Reserves transient bytes while one downlink batch replaces queued frames. /// Reserves transient bytes while one downlink batch replaces queued frames.
pub(crate) fn try_downlink_staging_budget( pub(crate) fn try_downlink_staging_budget(&self, bytes: usize) -> Option<OwnedSemaphorePermit> {
&self,
bytes: usize,
) -> Option<OwnedSemaphorePermit> {
let bytes = u32::try_from(bytes).ok()?; let bytes = u32::try_from(bytes).ok()?;
let permit = Arc::clone(&self.body_bytes) let permit = Arc::clone(&self.body_bytes)
.try_acquire_many_owned(bytes) .try_acquire_many_owned(bytes)
+14 -16
View File
@@ -4,8 +4,8 @@ use std::time::{Duration, Instant};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use super::negotiation::{CarrierClientClass, CarrierLearningContext};
use super::ProfileKey; use super::ProfileKey;
use super::negotiation::{CarrierClientClass, CarrierLearningContext};
use crate::config::{WebCarrier, WebCarrierNegotiationAggressiveness}; use crate::config::{WebCarrier, WebCarrierNegotiationAggressiveness};
const PROFILE_WEIGHT: i16 = 32; const PROFILE_WEIGHT: i16 = 32;
@@ -85,9 +85,7 @@ impl Evidence {
} }
aggregate.outcomes = aggregate.outcomes.saturating_add(bucket.outcomes); aggregate.outcomes = aggregate.outcomes.saturating_add(bucket.outcomes);
for (score, value) in aggregate.scores.iter_mut().zip(bucket.scores) { for (score, value) in aggregate.scores.iter_mut().zip(bucket.scores) {
*score = score *score = score.saturating_add(value).clamp(SCORE_MIN, SCORE_MAX);
.saturating_add(value)
.clamp(SCORE_MIN, SCORE_MAX);
} }
for cohort in bucket.cohorts.iter().flatten() { for cohort in bucket.cohorts.iter().flatten() {
if !aggregate.cohorts.contains(&Some(*cohort)) if !aggregate.cohorts.contains(&Some(*cohort))
@@ -262,10 +260,9 @@ impl CarrierLearning {
let user_agent_ready = user_agent let user_agent_ready = user_agent
.as_ref() .as_ref()
.is_some_and(|entry| entry.outcomes >= thresholds.user_agent); .is_some_and(|entry| entry.outcomes >= thresholds.user_agent);
let ip_ready = thresholds.ip.is_some_and(|minimum| { let ip_ready = thresholds
ip.as_ref() .ip
.is_some_and(|entry| entry.outcomes >= minimum) .is_some_and(|minimum| ip.as_ref().is_some_and(|entry| entry.outcomes >= minimum));
});
let mut scores = [0i16; 4]; let mut scores = [0i16; 4];
for carrier in WebCarrier::ALL { for carrier in WebCarrier::ALL {
let index = carrier.index(); let index = carrier.index();
@@ -274,11 +271,9 @@ impl CarrierLearning {
* PROFILE_WEIGHT; * PROFILE_WEIGHT;
} }
if user_agent_ready { if user_agent_ready {
scores[index] += i16::from( scores[index] +=
user_agent i16::from(user_agent.as_ref().map_or(0, |value| value.scores[index]))
.as_ref() * USER_AGENT_WEIGHT;
.map_or(0, |value| value.scores[index]),
) * USER_AGENT_WEIGHT;
} }
if ip_ready { if ip_ready {
scores[index] += scores[index] +=
@@ -362,7 +357,11 @@ impl CarrierLearning {
if !current { if !current {
continue; continue;
} }
if self.entries.get(&key).is_some_and(|entry| entry.is_live(slot)) { if self
.entries
.get(&key)
.is_some_and(|entry| entry.is_live(slot))
{
self.insertion_order.push_back((key, sequence)); self.insertion_order.push_back((key, sequence));
} else { } else {
self.entries.remove(&key); self.entries.remove(&key);
@@ -411,8 +410,7 @@ impl CarrierLearning {
let Some(insertion_sequence) = self.next_insertion_sequence() else { let Some(insertion_sequence) = self.next_insertion_sequence() else {
return; return;
}; };
self.entries self.entries.insert(key, Evidence::new(insertion_sequence));
.insert(key, Evidence::new(insertion_sequence));
self.insertion_order.push_back((key, insertion_sequence)); self.insertion_order.push_back((key, insertion_sequence));
if let Some(entry) = self.entries.get_mut(&key) { if let Some(entry) = self.entries.get_mut(&key) {
entry.update(slot, deltas, cohort); entry.update(slot, deltas, cohort);
+5 -4
View File
@@ -189,9 +189,7 @@ impl CarrierRequest {
/// Checks the complete idempotent identity of one exact attempt request. /// Checks the complete idempotent identity of one exact attempt request.
pub(crate) fn matches_attempt(self, other: Self) -> bool { pub(crate) fn matches_attempt(self, other: Self) -> bool {
self.matches_client(other) self.matches_client(other) && self.attempt == other.attempt && self.failure == other.failure
&& self.attempt == other.attempt
&& self.failure == other.failure
} }
fn capabilities_bits(self) -> Option<u8> { fn capabilities_bits(self) -> Option<u8> {
@@ -252,7 +250,10 @@ mod tests {
#[test] #[test]
fn invalid_candidate_or_attempt_counts_have_no_deadline_slot() { fn invalid_candidate_or_attempt_counts_have_no_deadline_slot() {
for (candidate_count, attempt) in [(0, 1), (5, 1), (1, 0), (1, 2), (3, 4)] { for (candidate_count, attempt) in [(0, 1), (5, 1), (1, 0), (1, 2), (3, 4)] {
assert_eq!(carrier_attempt_deadline_index(candidate_count, attempt), None); assert_eq!(
carrier_attempt_deadline_index(candidate_count, attempt),
None
);
} }
} }
} }
+38 -32
View File
@@ -7,14 +7,15 @@ use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use zeroize::Zeroizing; use zeroize::Zeroizing;
use super::negotiation::carrier_attempt_deadline_index;
use super::session_admission::admit_initial;
use super::state::{ use super::state::{
CarrierChainPhase, decrement_map, matching_profile, new_unique_token, profile_key, CarrierChainPhase, decrement_map, matching_profile, new_unique_token, profile_key,
remember_closed_token_locked, remove_expired_locked, remember_closed_token_locked, remove_expired_locked,
}; };
use super::negotiation::carrier_attempt_deadline_index;
use super::session_admission::admit_initial;
use super::{ use super::{
CarrierLearningContext, CarrierRequest, CreateResult, ManagerError, TokenHash, WebProcessRuntime, CarrierLearningContext, CarrierRequest, CreateResult, ManagerError, TokenHash,
WebProcessRuntime,
}; };
use crate::config::{WebCarrier, WebRuntimeProfile}; use crate::config::{WebCarrier, WebRuntimeProfile};
use crate::web::frame; use crate::web::frame;
@@ -62,7 +63,9 @@ impl WebProcessRuntime {
return Err(ManagerError::Authentication); return Err(ManagerError::Authentication);
} }
if entry.used { if entry.used {
if entry.carrier_deadline_at.is_some_and(|deadline| now >= deadline) if entry
.carrier_deadline_at
.is_some_and(|deadline| now >= deadline)
&& entry && entry
.session .session
.as_ref() .as_ref()
@@ -112,9 +115,8 @@ impl WebProcessRuntime {
token: entry.session_token.as_str().to_owned(), token: entry.session_token.as_str().to_owned(),
carrier: session.carrier(), carrier: session.carrier(),
attempt: carrier_request.attempt(), attempt: carrier_request.attempt(),
candidate_count: automatic.then(|| { candidate_count: automatic
u8::try_from(entry.carrier_candidates.len()).unwrap_or(4) .then(|| u8::try_from(entry.carrier_candidates.len()).unwrap_or(4)),
}),
deadline_secs: automatic deadline_secs: automatic
.then_some(entry.profile.carrier_negotiation_deadlines_secs[3]), .then_some(entry.profile.carrier_negotiation_deadlines_secs[3]),
carrier_state: automatic.then_some(carrier_state), carrier_state: automatic.then_some(carrier_state),
@@ -139,14 +141,16 @@ impl WebProcessRuntime {
CarrierChainPhase::CommittedPendingHealth | CarrierChainPhase::Healthy CarrierChainPhase::CommittedPendingHealth | CarrierChainPhase::Healthy
) )
{ {
return Err(if matches!( return Err(
if matches!(
entry.carrier_phase, entry.carrier_phase,
CarrierChainPhase::CommittedPendingHealth | CarrierChainPhase::Healthy CarrierChainPhase::CommittedPendingHealth | CarrierChainPhase::Healthy
) { ) {
ManagerError::Committed ManagerError::Committed
} else { } else {
ManagerError::Protocol ManagerError::Protocol
}); },
);
} }
let Some(carrier) = entry let Some(carrier) = entry
.carrier_candidates .carrier_candidates
@@ -155,8 +159,8 @@ impl WebProcessRuntime {
else { else {
return Err(ManagerError::Protocol); return Err(ManagerError::Protocol);
}; };
let candidate_count = u8::try_from(entry.carrier_candidates.len()) let candidate_count =
.map_err(|_| ManagerError::Protocol)?; u8::try_from(entry.carrier_candidates.len()).map_err(|_| ManagerError::Protocol)?;
let deadline_index = carrier_attempt_deadline_index(candidate_count, next_attempt) let deadline_index = carrier_attempt_deadline_index(candidate_count, next_attempt)
.ok_or(ManagerError::Protocol)?; .ok_or(ManagerError::Protocol)?;
if entry.carrier_started_at.is_some_and(|started| { if entry.carrier_started_at.is_some_and(|started| {
@@ -211,8 +215,8 @@ impl WebProcessRuntime {
if carrier_request.is_automatic() && !profile.carrier_negotiation_enabled { if carrier_request.is_automatic() && !profile.carrier_negotiation_enabled {
return Err(ManagerError::Protocol); return Err(ManagerError::Protocol);
} }
let capability_selection = carrier_request.uses_capabilities() let capability_selection =
&& profile.carrier_negotiation_enabled; carrier_request.uses_capabilities() && profile.carrier_negotiation_enabled;
let learning_policy = ( let learning_policy = (
config.web.carrier_negotiation_enabled() && config.web.carrier_learning, config.web.carrier_negotiation_enabled() && config.web.carrier_learning,
config.web.carrier_negotiation_aggressiveness, config.web.carrier_negotiation_aggressiveness,
@@ -222,11 +226,9 @@ impl WebProcessRuntime {
&& profile.carrier_learning && profile.carrier_learning
{ {
let learning = self.learning.lock(); let learning = self.learning.lock();
if let Some(epoch) = learning.epoch_for_policy( if let Some(epoch) =
learning_policy.0, learning.epoch_for_policy(learning_policy.0, learning_policy.1, learning_policy.2)
learning_policy.1, {
learning_policy.2,
) {
let (candidates, scores) = learning.rank( let (candidates, scores) = learning.rank(
now, now,
&profile.carriers, &profile.carriers,
@@ -259,8 +261,7 @@ impl WebProcessRuntime {
[0; 4], [0; 4],
None, None,
) )
} else if carrier_request.uses_capabilities() } else if carrier_request.uses_capabilities() && !carrier_request.supports(profile.carrier)
&& !carrier_request.supports(profile.carrier)
{ {
return Err(ManagerError::Protocol); return Err(ManagerError::Protocol);
} else { } else {
@@ -276,9 +277,9 @@ impl WebProcessRuntime {
self.limit_hits.fetch_add(1, Ordering::Relaxed); self.limit_hits.fetch_add(1, Ordering::Relaxed);
return Err(ManagerError::Limit); return Err(ManagerError::Limit);
}; };
let carrier_deadline_at = carrier_request.is_automatic().then_some( let carrier_deadline_at = carrier_request
now + Duration::from_secs(profile.carrier_negotiation_deadlines_secs[3]), .is_automatic()
); .then_some(now + Duration::from_secs(profile.carrier_negotiation_deadlines_secs[3]));
let learning_context = learning_epoch.map(|epoch| CarrierLearningContext { let learning_context = learning_epoch.map(|epoch| CarrierLearningContext {
profile_key, profile_key,
client_ip, client_ip,
@@ -336,9 +337,7 @@ impl WebProcessRuntime {
token: session_token, token: session_token,
carrier, carrier,
attempt: carrier_request.attempt(), attempt: carrier_request.attempt(),
candidate_count: carrier_request candidate_count: carrier_request.is_automatic().then_some(candidate_count),
.is_automatic()
.then_some(candidate_count),
deadline_secs: carrier_request deadline_secs: carrier_request
.is_automatic() .is_automatic()
.then_some(profile.carrier_negotiation_deadlines_secs[3]), .then_some(profile.carrier_negotiation_deadlines_secs[3]),
@@ -494,9 +493,7 @@ impl WebProcessRuntime {
token: session_token, token: session_token,
carrier: replacement.carrier, carrier: replacement.carrier,
attempt: Some(replacement.attempt), attempt: Some(replacement.attempt),
candidate_count: Some( candidate_count: Some(u8::try_from(entry.carrier_candidates.len()).unwrap_or(4)),
u8::try_from(entry.carrier_candidates.len()).unwrap_or(4),
),
deadline_secs: Some(entry.profile.carrier_negotiation_deadlines_secs[3]), deadline_secs: Some(entry.profile.carrier_negotiation_deadlines_secs[3]),
carrier_state: Some(CarrierChainPhase::Provisional.as_str()), carrier_state: Some(CarrierChainPhase::Provisional.as_str()),
}; };
@@ -512,7 +509,10 @@ impl WebProcessRuntime {
replacement.old_session.carrier(), replacement.old_session.carrier(),
replacement.attempt - 1, replacement.attempt - 1,
replacement.scores, replacement.scores,
replacement.request.failure().map(|failure| failure.as_str()), replacement
.request
.failure()
.map(|failure| failure.as_str()),
); );
self.trace.record_carrier_lifecycle( self.trace.record_carrier_lifecycle(
client_ip, client_ip,
@@ -522,7 +522,10 @@ impl WebProcessRuntime {
replacement.old_session.carrier(), replacement.old_session.carrier(),
replacement.attempt - 1, replacement.attempt - 1,
replacement.scores, replacement.scores,
replacement.request.failure().map(|failure| failure.as_str()), replacement
.request
.failure()
.map(|failure| failure.as_str()),
); );
self.trace.record_carrier_lifecycle( self.trace.record_carrier_lifecycle(
client_ip, client_ip,
@@ -540,7 +543,10 @@ impl WebProcessRuntime {
identity, identity,
TraceLifecycleEvent::SessionCreated, TraceLifecycleEvent::SessionCreated,
None, None,
replacement.request.failure().map(|failure| failure.as_str()), replacement
.request
.failure()
.map(|failure| failure.as_str()),
); );
Ok(result) Ok(result)
} }
+3 -8
View File
@@ -357,9 +357,7 @@ fn select_victim(
let requester_usage = runtime.data_budget.owner_usage(owner); let requester_usage = runtime.data_budget.owner_usage(owner);
let now = runtime.websocket_tick(); let now = runtime.websocket_tick();
let mut registry = runtime.websockets.lock(); let mut registry = runtime.websockets.lock();
if claim if claim && registry.evictions_in_flight >= runtime.limits.max_websocket_evictions_in_flight {
&& registry.evictions_in_flight >= runtime.limits.max_websocket_evictions_in_flight
{
return None; return None;
} }
let selected = registry let selected = registry
@@ -408,9 +406,7 @@ fn select_pressure_victim(
claim: bool, claim: bool,
) -> Option<Arc<WebSocketEntry>> { ) -> Option<Arc<WebSocketEntry>> {
let mut registry = runtime.websockets.lock(); let mut registry = runtime.websockets.lock();
if claim if claim && registry.evictions_in_flight >= runtime.limits.max_websocket_evictions_in_flight {
&& registry.evictions_in_flight >= runtime.limits.max_websocket_evictions_in_flight
{
return None; return None;
} }
let selected = registry let selected = registry
@@ -447,8 +443,7 @@ fn claim_stale_victims(runtime: &WebProcessRuntime, now: u64) -> Vec<Arc<WebSock
.values() .values()
.filter(|entry| !entry.closing.load(Ordering::Acquire)) .filter(|entry| !entry.closing.load(Ordering::Acquire))
.filter(|entry| { .filter(|entry| {
now.saturating_sub(entry.last_peer_tick.load(Ordering::Acquire)) now.saturating_sub(entry.last_peer_tick.load(Ordering::Acquire)) >= dead_after(entry)
>= dead_after(entry)
}) })
.take(available) .take(available)
.cloned() .cloned()
-1
View File
@@ -482,7 +482,6 @@ impl WebSession {
.upgrade() .upgrade()
.map(|manager| manager.budget_notify()) .map(|manager| manager.budget_notify())
} }
} }
fn inbound_queue_cost(queue: &VecDeque<InboundChunk>) -> (usize, usize) { fn inbound_queue_cost(queue: &VecDeque<InboundChunk>) -> (usize, usize) {
+2 -4
View File
@@ -156,10 +156,8 @@ impl WebSession {
let fits = if control { let fits = if control {
bytes <= self.limits.control_bytes_per_session bytes <= self.limits.control_bytes_per_session
&& items <= item_reserve && items <= item_reserve
&& pending_bytes && pending_bytes <= self.limits.pending_bytes_per_session.saturating_sub(bytes)
<= self.limits.pending_bytes_per_session.saturating_sub(bytes) && pending_items <= self.limits.pending_items_per_session.saturating_sub(items)
&& pending_items
<= self.limits.pending_items_per_session.saturating_sub(items)
&& pending_control_bytes && pending_control_bytes
<= self.limits.control_bytes_per_session.saturating_sub(bytes) <= self.limits.control_bytes_per_session.saturating_sub(bytes)
&& pending_control_items <= item_reserve.saturating_sub(items) && pending_control_items <= item_reserve.saturating_sub(items)
+1 -2
View File
@@ -5,8 +5,7 @@ use std::sync::Arc;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use crate::config::{ use crate::config::{
ProxyConfig, WebCarrier, WebLimitsConfig, WebRuntimeProfile, WebSecretMode, ProxyConfig, WebCarrier, WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
WebTimeoutsConfig,
}; };
use crate::maestro::generation::test_runtime_generation; use crate::maestro::generation::test_runtime_generation;
use crate::web::manager::WebProcessRuntime; use crate::web::manager::WebProcessRuntime;
+2 -4
View File
@@ -56,8 +56,7 @@ impl WebSession {
&& only_late_frames(&frames) && only_late_frames(&frames)
{ {
return if self.automatic_carrier return if self.automatic_carrier
&& state.negotiation_phase && state.negotiation_phase != super::SessionNegotiationPhase::Committed
!= super::SessionNegotiationPhase::Committed
{ {
Err(ManagerError::Backpressure) Err(ManagerError::Backpressure)
} else { } else {
@@ -154,8 +153,7 @@ impl WebSession {
} }
} }
if applied { if applied {
(committed, healthy) = (committed, healthy) = self.record_uplink_progress_locked(&mut state, progress);
self.record_uplink_progress_locked(&mut state, progress);
} }
applied.then_some(sequence).ok_or(ManagerError::Closed) applied.then_some(sequence).ok_or(ManagerError::Closed)
}; };
+2 -7
View File
@@ -119,8 +119,7 @@ impl WebSession {
return Err(ManagerError::Closed); return Err(ManagerError::Closed);
} }
let carrier_health_eligible = lane_id != 0 let carrier_health_eligible = lane_id != 0
&& state.negotiation_phase && state.negotiation_phase == super::SessionNegotiationPhase::Committed;
== super::SessionNegotiationPhase::Committed;
let Some(lane) = state.carrier_lanes.get_mut(&lane_id) else { let Some(lane) = state.carrier_lanes.get_mut(&lane_id) else {
return Ok(PollResult { return Ok(PollResult {
body: Bytes::new(), body: Bytes::new(),
@@ -231,11 +230,7 @@ impl WebSession {
} }
} }
async fn wait_for_lane_open( async fn wait_for_lane_open(&self, lane_id: u32, cursor: u64) -> Result<bool, ManagerError> {
&self,
lane_id: u32,
cursor: u64,
) -> Result<bool, ManagerError> {
let wait = { let wait = {
let mut state = self.state.lock(); let mut state = self.state.lock();
if state.closed { if state.closed {
+6 -8
View File
@@ -6,8 +6,7 @@ use bytes::BytesMut;
use super::*; use super::*;
use crate::config::{ use crate::config::{
ProxyConfig, WebCarrier, WebLimitsConfig, WebRuntimeProfile, WebSecretMode, ProxyConfig, WebCarrier, WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
WebTimeoutsConfig,
}; };
use crate::maestro::generation::test_runtime_generation; use crate::maestro::generation::test_runtime_generation;
use crate::web::manager::WebProcessRuntime; use crate::web::manager::WebProcessRuntime;
@@ -187,7 +186,9 @@ fn lane_uplink_sequences_are_independent_and_exactly_once() {
{ {
let mut state = session.state.lock(); let mut state = session.state.lock();
for lane_id in [51, 52] { for lane_id in [51, 52] {
state.carrier_lanes.insert(lane_id, CarrierLane::new(u64::from(lane_id))); state
.carrier_lanes
.insert(lane_id, CarrierLane::new(u64::from(lane_id)));
state.closed_streams.insert(lane_id); state.closed_streams.insert(lane_id);
} }
} }
@@ -274,11 +275,8 @@ fn tombstone_eviction_releases_lane_budget_and_accepts_late_frames() {
#[test] #[test]
fn automatic_lane_does_not_ack_a_missing_lane_without_real_progress() { fn automatic_lane_does_not_ack_a_missing_lane_without_real_progress() {
let session = new_session_with_automatic( let session =
WebLimitsConfig::default(), new_session_with_automatic(WebLimitsConfig::default(), std::sync::Weak::new(), true);
std::sync::Weak::new(),
true,
);
let late = frame::encode(FrameType::Data, 7, b"late"); let late = frame::encode(FrameType::Data, 7, b"late");
assert_eq!( assert_eq!(
+2 -12
View File
@@ -138,12 +138,7 @@ impl WebSession {
if let Some(batch) = state.unacked.take() { if let Some(batch) = state.unacked.take() {
batch.lease.detach(); batch.lease.detach();
self.release_local_locked(&mut state, batch.data_bytes, batch.data_items, false); self.release_local_locked(&mut state, batch.data_bytes, batch.data_items, false);
self.release_local_locked( self.release_local_locked(&mut state, batch.control_bytes, batch.control_items, true);
&mut state,
batch.control_bytes,
batch.control_items,
true,
);
} }
let mut lane_data_bytes = 0usize; let mut lane_data_bytes = 0usize;
let mut lane_data_items = 0usize; let mut lane_data_items = 0usize;
@@ -160,12 +155,7 @@ impl WebSession {
} }
} }
self.release_local_locked(&mut state, lane_data_bytes, lane_data_items, false); self.release_local_locked(&mut state, lane_data_bytes, lane_data_items, false);
self.release_local_locked( self.release_local_locked(&mut state, lane_control_bytes, lane_control_items, true);
&mut state,
lane_control_bytes,
lane_control_items,
true,
);
state.carrier_lanes.clear(); state.carrier_lanes.clear();
let control_bytes = state.pending_control_bytes; let control_bytes = state.pending_control_bytes;
let control_items = state.pending_control_items; let control_items = state.pending_control_items;
+2 -3
View File
@@ -93,9 +93,8 @@ impl WebSession {
let now = Instant::now(); let now = Instant::now();
let committed = if state.negotiation_phase == SessionNegotiationPhase::Uncommitted { let committed = if state.negotiation_phase == SessionNegotiationPhase::Uncommitted {
state.negotiation_phase = SessionNegotiationPhase::Committed; state.negotiation_phase = SessionNegotiationPhase::Committed;
state.carrier_health_due_at = Some( state.carrier_health_due_at =
now + Duration::from_secs(self.timeouts.carrier_health_secs), Some(now + Duration::from_secs(self.timeouts.carrier_health_secs));
);
true true
} else { } else {
false false
+8 -4
View File
@@ -43,8 +43,10 @@ impl ResidentCounters {
} }
fn add(&self, counts: PendingCounts) { fn add(&self, counts: PendingCounts) {
self.data_bytes.fetch_add(counts.data_bytes, Ordering::AcqRel); self.data_bytes
self.data_items.fetch_add(counts.data_items, Ordering::AcqRel); .fetch_add(counts.data_bytes, Ordering::AcqRel);
self.data_items
.fetch_add(counts.data_items, Ordering::AcqRel);
self.control_bytes self.control_bytes
.fetch_add(counts.control_bytes, Ordering::AcqRel); .fetch_add(counts.control_bytes, Ordering::AcqRel);
self.control_items self.control_items
@@ -52,8 +54,10 @@ impl ResidentCounters {
} }
fn remove(&self, counts: PendingCounts) { fn remove(&self, counts: PendingCounts) {
self.data_bytes.fetch_sub(counts.data_bytes, Ordering::AcqRel); self.data_bytes
self.data_items.fetch_sub(counts.data_items, Ordering::AcqRel); .fetch_sub(counts.data_bytes, Ordering::AcqRel);
self.data_items
.fetch_sub(counts.data_items, Ordering::AcqRel);
self.control_bytes self.control_bytes
.fetch_sub(counts.control_bytes, Ordering::AcqRel); .fetch_sub(counts.control_bytes, Ordering::AcqRel);
self.control_items self.control_items
+5 -3
View File
@@ -139,8 +139,7 @@ impl WebSession {
} else { } else {
state.last_up_sequence = sequence; state.last_up_sequence = sequence;
state.last_up_digest = digest; state.last_up_digest = digest;
(committed, healthy) = (committed, healthy) = self.record_uplink_progress_locked(&mut state, progress);
self.record_uplink_progress_locked(&mut state, progress);
Ok((sequence, progress.any())) Ok((sequence, progress.any()))
} }
}; };
@@ -532,7 +531,10 @@ mod tests {
let session = session_with_automatic(true); let session = session_with_automatic(true);
let body = frame::encode(FrameType::Pong, 0, &[]); let body = frame::encode(FrameType::Pong, 0, &[]);
assert_eq!(session.process_up(1, &body), Err(ManagerError::Backpressure)); assert_eq!(
session.process_up(1, &body),
Err(ManagerError::Backpressure)
);
assert!(!session.is_carrier_committed()); assert!(!session.is_carrier_committed());
assert!(!session.state.lock().closed); assert!(!session.state.lock().closed);
} }
+1 -2
View File
@@ -273,8 +273,7 @@ impl WebSession {
} }
state.last_activity = Instant::now(); state.last_activity = Instant::now();
if applied { if applied {
(committed, healthy) = (committed, healthy) = self.record_uplink_progress_locked(&mut state, progress);
self.record_uplink_progress_locked(&mut state, progress);
} }
applied applied
.then_some(progress.any()) .then_some(progress.any())