mirror of
https://github.com/telemt/telemt.git
synced 2026-11-01 05:12:02 +03:00
Concurrent Uplinks w/ wrong single-flight recovery fixes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -337,7 +337,7 @@ pub struct WebTimeoutsConfig {
|
|||||||
/// Lifetime of an unused bootstrap credential and closed-token replay marker.
|
/// Lifetime of an unused bootstrap credential and closed-token replay marker.
|
||||||
#[serde(default = "default_web_bootstrap_lifetime_secs")]
|
#[serde(default = "default_web_bootstrap_lifetime_secs")]
|
||||||
pub bootstrap_lifetime_secs: u64,
|
pub bootstrap_lifetime_secs: u64,
|
||||||
/// Maximum carrier inactivity before a session is closed.
|
/// Maximum validated peer inactivity before a session is closed.
|
||||||
#[serde(default = "default_web_reconnect_grace_secs")]
|
#[serde(default = "default_web_reconnect_grace_secs")]
|
||||||
pub reconnect_grace_secs: u64,
|
pub reconnect_grace_secs: u64,
|
||||||
/// Maximum idle lifetime of a WEB HTTP keep-alive connection.
|
/// Maximum idle lifetime of a WEB HTTP keep-alive connection.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function cancelBatch(lease){if(!lease||lease.settled)return;buffers.cancelBatch(
|
|||||||
const attemptHeaders=(attempt,failure)=>negotiationEnabled?Object.assign({'X-Carrier-Capabilities':carrierCapabilities,'X-Carrier-Attempt':String(attempt)},failure?{'X-Carrier-Failure':failure}:{}):{};
|
const attemptHeaders=(attempt,failure)=>negotiationEnabled?Object.assign({'X-Carrier-Capabilities':carrierCapabilities,'X-Carrier-Attempt':String(attempt)},failure?{'X-Carrier-Failure':failure}:{}):{};
|
||||||
function finishOldRecovery(){
|
function finishOldRecovery(){
|
||||||
recoveryReplaced=false;status('connected','committed','',0);
|
recoveryReplaced=false;status('connected','committed','',0);
|
||||||
for(const data of recoveryPending.splice(0)){release(data.byteLength,1,null);queueCarrier(data)}
|
while(recoveryPending.length&&!closed){const data=recoveryPending.shift();release(data.byteLength,1,null);queueCarrier(data)}
|
||||||
}
|
}
|
||||||
function rejectRecoveryCommit(error){
|
function rejectRecoveryCommit(error){
|
||||||
const commit=recoveryCommit;if(!commit)return;recoveryCommit=null;
|
const commit=recoveryCommit;if(!commit)return;recoveryCommit=null;
|
||||||
@@ -86,7 +86,7 @@ function replaceCarrier(policy,signal,remaining){
|
|||||||
return new Promise((resolve,reject)=>{
|
return new Promise((resolve,reject)=>{
|
||||||
const abort=()=>rejectRecoveryCommit(failure('timeout','recovery deadline'));
|
const abort=()=>rejectRecoveryCommit(failure('timeout','recovery deadline'));
|
||||||
recoveryCommit={resolve,reject,signal,abort};signal.addEventListener('abort',abort,{once:true});
|
recoveryCommit={resolve,reject,signal,abort};signal.addEventListener('abort',abort,{once:true});
|
||||||
if(remaining()<=0){abort();return}createSession(attemptEpoch);
|
if(signal.aborted||remaining()<=0){abort();return}createSession(attemptEpoch);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function recoverTransport(error,replay){
|
function recoverTransport(error,replay){
|
||||||
@@ -239,14 +239,15 @@ function commitCarrier(probe,epoch){
|
|||||||
status('connected','committed','',0);
|
status('connected','committed','',0);
|
||||||
if(carrier==='https')poll();
|
if(carrier==='https')poll();
|
||||||
else if(carrier==='https-lanes'){const lane=lanes.get(probe.id);if(lane&&!lane.polling)pollLane(lane)}
|
else if(carrier==='https-lanes'){const lane=lanes.get(probe.id);if(lane&&!lane.polling)pollLane(lane)}
|
||||||
for(const data of pending.splice(0)){release(data.byteLength,1,null);queueCarrier(data)}
|
while(pending.length&&!closed){const data=pending.shift();release(data.byteLength,1,null);queueCarrier(data)}
|
||||||
resolveRecoveryCommit();
|
resolveRecoveryCommit();
|
||||||
}
|
}
|
||||||
function queueCarrier(data){
|
function queueCarrier(data){
|
||||||
|
if(closed)return;
|
||||||
try{
|
try{
|
||||||
if(carrier==='https')queueUp(data);
|
if(carrier==='https')queueUp(data);
|
||||||
else if(carrier==='websocket')queueSocket(data);
|
else if(carrier==='websocket')queueSocket(data);
|
||||||
else for(const value of splitFrames(data))queueLane(value);
|
else for(const value of splitFrames(data)){if(closed)break;queueLane(value)}
|
||||||
}catch(error){fail('protocol')}
|
}catch(error){fail('protocol')}
|
||||||
}
|
}
|
||||||
function queueUp(data){if(!reserve(data,null)){fail('capacity');return}upPending.push(data);runUp()}
|
function queueUp(data){if(!reserve(data,null)){fail('capacity');return}upPending.push(data);runUp()}
|
||||||
@@ -262,12 +263,15 @@ async function runUp(){
|
|||||||
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','uplink acknowledgement rejected');
|
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','uplink acknowledgement rejected');
|
||||||
break;
|
break;
|
||||||
}catch(error){
|
}catch(error){
|
||||||
|
let replayed=false;
|
||||||
const recovered=await recoverTransport(error,async(signal,remaining)=>{
|
const recovered=await recoverTransport(error,async(signal,remaining)=>{
|
||||||
const response=await request('/api/v1/up',options('POST',token,lease.body,{'X-Up-Seq':sequence},signal),remaining,2);
|
const response=await request('/api/v1/up',options('POST',token,lease.body,{'X-Up-Seq':sequence},signal),remaining,2);
|
||||||
if(response.status!==204)throw failure('http','uplink replay rejected');
|
if(response.status!==204)throw failure('http','uplink replay rejected');
|
||||||
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','uplink replay acknowledgement rejected');
|
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','uplink replay acknowledgement rejected');
|
||||||
|
replayed=true;
|
||||||
});
|
});
|
||||||
if(!recovered||closed||lease.cancelled||sessionToken!==token)return;
|
if(!recovered||closed||lease.cancelled||sessionToken!==token)return;
|
||||||
|
if(!replayed)continue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,12 +420,15 @@ async function runLaneUp(lane){
|
|||||||
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','lane uplink acknowledgement rejected');
|
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','lane uplink acknowledgement rejected');
|
||||||
break;
|
break;
|
||||||
}catch(error){
|
}catch(error){
|
||||||
|
let replayed=false;
|
||||||
const recovered=await recoverTransport(error,async(signal,remaining)=>{
|
const recovered=await recoverTransport(error,async(signal,remaining)=>{
|
||||||
const response=await request('/api/v1/up',options('POST',token,lease.body,{'X-Up-Seq':sequence,'X-Lane-ID':laneID},signal),remaining,2);
|
const response=await request('/api/v1/up',options('POST',token,lease.body,{'X-Up-Seq':sequence,'X-Lane-ID':laneID},signal),remaining,2);
|
||||||
if(response.status!==204)throw failure('http','lane uplink replay rejected');
|
if(response.status!==204)throw failure('http','lane uplink replay rejected');
|
||||||
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','lane uplink replay acknowledgement rejected');
|
if(response.headers.get('X-Up-Ack')!==sequence)throw failure('protocol','lane uplink replay acknowledgement rejected');
|
||||||
|
replayed=true;
|
||||||
});
|
});
|
||||||
if(!recovered||closed||lease.cancelled||sessionToken!==token||lanes.get(lane.id)!==lane)return;
|
if(!recovered||closed||lease.cancelled||sessionToken!==token||lanes.get(lane.id)!==lane)return;
|
||||||
|
if(!replayed)continue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ impl WebProcessRuntime {
|
|||||||
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()),
|
||||||
};
|
};
|
||||||
|
let predecessor_session_id = entry.predecessor_session_id;
|
||||||
if let Some(index) = state.session_index.get_mut(&replacement.trace_session_id)
|
if let Some(index) = state.session_index.get_mut(&replacement.trace_session_id)
|
||||||
&& index.session_hash == old_hash
|
&& index.session_hash == old_hash
|
||||||
{
|
{
|
||||||
@@ -198,7 +199,7 @@ impl WebProcessRuntime {
|
|||||||
replacement.scores,
|
replacement.scores,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
self.trace.record_lifecycle(
|
self.trace.record_lifecycle_with_context(
|
||||||
None,
|
None,
|
||||||
Some(client_ip),
|
Some(client_ip),
|
||||||
identity,
|
identity,
|
||||||
@@ -208,6 +209,10 @@ impl WebProcessRuntime {
|
|||||||
.request
|
.request
|
||||||
.failure()
|
.failure()
|
||||||
.map(|failure| failure.as_str()),
|
.map(|failure| failure.as_str()),
|
||||||
|
crate::web::trace::TraceLifecycleContext {
|
||||||
|
peer_gap_ms: None,
|
||||||
|
predecessor_session_id,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,6 +319,11 @@ impl WebSession {
|
|||||||
return Err(ManagerError::Limit);
|
return Err(ManagerError::Limit);
|
||||||
};
|
};
|
||||||
state.lane_open_waits += 1;
|
state.lane_open_waits += 1;
|
||||||
|
self.touch_peer_locked(
|
||||||
|
&mut state,
|
||||||
|
Instant::now(),
|
||||||
|
WebSessionLifecycleObservation::HttpActivityAfterGap,
|
||||||
|
);
|
||||||
LaneOpenWaitGuard {
|
LaneOpenWaitGuard {
|
||||||
session: self,
|
session: self,
|
||||||
_auxiliary: auxiliary,
|
_auxiliary: auxiliary,
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ pub(crate) struct TraceWebSocketRecord {
|
|||||||
pub(crate) lane_id: Option<u32>,
|
pub(crate) lane_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closed WEB lifecycle event category.
|
/// WEB lifecycle event category.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub(crate) enum TraceLifecycleEvent {
|
pub(crate) enum TraceLifecycleEvent {
|
||||||
/// A bridge bootstrap was issued.
|
/// A bridge bootstrap was issued.
|
||||||
@@ -329,7 +329,7 @@ pub(crate) struct TraceLifecycleRecord {
|
|||||||
pub(crate) event: TraceLifecycleEvent,
|
pub(crate) event: TraceLifecycleEvent,
|
||||||
/// Logical stream identifier when applicable.
|
/// Logical stream identifier when applicable.
|
||||||
pub(crate) stream_id: Option<u32>,
|
pub(crate) stream_id: Option<u32>,
|
||||||
/// Closed outcome or rejection reason.
|
/// Terminal outcome or rejection reason.
|
||||||
pub(crate) reason: Option<&'static str>,
|
pub(crate) reason: Option<&'static str>,
|
||||||
/// Carrier negotiation detail when this is a carrier lifecycle event.
|
/// Carrier negotiation detail when this is a carrier lifecycle event.
|
||||||
pub(crate) carrier: Option<TraceCarrierDetail>,
|
pub(crate) carrier: Option<TraceCarrierDetail>,
|
||||||
|
|||||||
Reference in New Issue
Block a user