@@ -100,6 +100,7 @@ const DEFAULT_SYNC_HEARTBEAT_INTERVAL_MS = 30_000;
100100const DEFAULT_SYNC_POLL_INTERVAL_MS = 400 ;
101101const DEFAULT_BRAIN_STATUS_INTERVAL_MS = 5_000 ;
102102const DEFAULT_TERMINAL_SNAPSHOT_BYTES = 220_000 ;
103+ const PEER_BACKPRESSURE_BYTES = 4 * 1024 * 1024 ;
103104const LANE_PRESENCE_TTL_MS = 60_000 ;
104105const SYNC_MDNS_SERVICE_TYPE = "ade-sync" ;
105106export const SYNC_TAILNET_DISCOVERY_SERVICE_NAME = "svc:ade-sync" ;
@@ -753,6 +754,13 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
753754 const sentAt = nowIso ( ) ;
754755 for ( const peer of peers ) {
755756 if ( ! peer . authenticated || peer . ws . readyState !== WebSocket . OPEN ) continue ;
757+ if ( isPeerBackpressured ( peer ) ) {
758+ args . logger . debug ( "sync_host.heartbeat_deferred_backpressure" , {
759+ peerDeviceId : peer . metadata ?. deviceId ?? null ,
760+ bufferedAmount : peer . ws . bufferedAmount ,
761+ } ) ;
762+ continue ;
763+ }
756764 if ( peer . awaitingHeartbeatAt ) {
757765 peer . missedHeartbeatCount += 1 ;
758766 if ( peer . missedHeartbeatCount >= 2 ) {
@@ -1095,10 +1103,23 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
10951103 }
10961104 } ;
10971105
1098- function send < TPayload > ( ws : WebSocket , type : SyncEnvelope [ "type" ] , payload : TPayload , requestId ?: string | null ) : void {
1106+ function send < TPayload > ( target : WebSocket | PeerState , type : SyncEnvelope [ "type" ] , payload : TPayload , requestId ?: string | null ) : void {
1107+ const ws = target instanceof WebSocket ? target : target . ws ;
1108+ if ( ws . readyState !== WebSocket . OPEN ) return ;
1109+ // Drop sends to backpressured peers as the default — most envelopes are
1110+ // either replayable (chat events / changesets re-derived from db state) or
1111+ // tolerable to lose (acks, status pings). Routes that *must* deliver under
1112+ // backpressure should call ws.send / sendAndWait directly.
1113+ if ( target instanceof WebSocket ? ws . bufferedAmount >= PEER_BACKPRESSURE_BYTES : isPeerBackpressured ( target ) ) {
1114+ return ;
1115+ }
10991116 ws . send ( encodeSyncEnvelope ( { type, payload, requestId, compressionThresholdBytes } ) ) ;
11001117 }
11011118
1119+ function isPeerBackpressured ( peer : PeerState ) : boolean {
1120+ return peer . ws . bufferedAmount >= PEER_BACKPRESSURE_BYTES ;
1121+ }
1122+
11021123 function sendAndWait < TPayload > (
11031124 ws : WebSocket ,
11041125 type : SyncEnvelope [ "type" ] ,
@@ -1421,6 +1442,7 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
14211442
14221443 for ( const peer of peers ) {
14231444 if ( ! peer . authenticated || peer . ws . readyState !== WebSocket . OPEN ) continue ;
1445+ if ( isPeerBackpressured ( peer ) ) continue ;
14241446 for ( const sessionId of peer . subscribedChatSessionIds ) {
14251447 const session = args . sessionService . get ( sessionId ) ;
14261448 if ( ! session ?. transcriptPath ) continue ;
@@ -1441,6 +1463,7 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
14411463 function broadcastChatEvent ( event : AgentChatEventEnvelope ) : void {
14421464 for ( const peer of peers ) {
14431465 if ( ! peer . authenticated || peer . ws . readyState !== WebSocket . OPEN ) continue ;
1466+ if ( isPeerBackpressured ( peer ) ) continue ;
14441467 if ( ! peer . subscribedChatSessionIds . has ( event . sessionId ) ) continue ;
14451468 if ( ! rememberChatEventSent ( peer , event ) ) continue ;
14461469 send ( peer . ws , "chat_event" , event ) ;
@@ -1452,6 +1475,7 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
14521475 const currentDbVersion = args . db . sync . getDbVersion ( ) ;
14531476 for ( const peer of peers ) {
14541477 if ( ! peer . authenticated || ! peer . metadata || peer . ws . readyState !== WebSocket . OPEN ) continue ;
1478+ if ( isPeerBackpressured ( peer ) ) continue ;
14551479 if ( currentDbVersion <= peer . lastKnownServerDbVersion ) continue ;
14561480 const changes = args . db . sync
14571481 . exportChangesSince ( peer . lastKnownServerDbVersion )
@@ -2394,6 +2418,7 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
23942418 } ;
23952419 for ( const peer of peers ) {
23962420 if ( ! peer . authenticated || ! peer . subscribedSessionIds . has ( event . sessionId ) || peer . ws . readyState !== WebSocket . OPEN ) continue ;
2421+ if ( isPeerBackpressured ( peer ) ) continue ;
23972422 send ( peer . ws , "terminal_data" , payload ) ;
23982423 }
23992424 } ,
@@ -2407,6 +2432,7 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
24072432 } ;
24082433 for ( const peer of peers ) {
24092434 if ( ! peer . authenticated || ! peer . subscribedSessionIds . has ( event . sessionId ) || peer . ws . readyState !== WebSocket . OPEN ) continue ;
2435+ if ( isPeerBackpressured ( peer ) ) continue ;
24102436 send ( peer . ws , "terminal_exit" , payload ) ;
24112437 }
24122438 } ,
0 commit comments