fix(#4313): guard EventSource.close() calls to suppress ERR_ABORTED noise
Add readyState !== CLOSED (2) guards to all EventSource.close() calls across messages.js, sessions.js, panels.js, and terminal.js. Chromium reports net::ERR_ABORTED when close() is called on an already-closed EventSource, polluting the console during normal lifecycle transitions (session switch, panel close, stream teardown). Key hotspots: - closeLiveStream() double-close via onerror → _closeSource chain - _closeSessionEventsSSE, _gatewaySSE.onerror missing try/catch - _kanbanStopPolling, terminal close handlers 20 close() calls guarded across 4 files, no structural changes to stream lifecycle.
This commit is contained in:
1 parent
bc11cfe6d2
commit
d812f70116
4 files changed
+20
-20
No files matched your search
+9
-9
@@ -1418,7 +1418,7 @@ function closeLiveStream(sessionId, streamId, source){
|
||||
// reattach path will rebuild it from INFLIGHT/server state if the user returns.
|
||||
if(typeof _clearLiveRunStatusTimer==='function') _clearLiveRunStatusTimer(sessionId);
|
||||
if(typeof hideLiveRunStatus==='function') hideLiveRunStatus(sessionId);
|
||||
try{live.source.close();}catch(_){ }
|
||||
try{if(live.source&&live.source.readyState!==2)live.source.close();}catch(_){ }
|
||||
delete LIVE_STREAMS[sessionId];
|
||||
// closeLiveStream() is called during session-switch teardown for any session
|
||||
// the user is no longer viewing. The stream is still active on the server,
|
||||
@@ -2924,7 +2924,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
function _wireSSE(source){
|
||||
const existingLive=LIVE_STREAMS[activeSid];
|
||||
if(existingLive&&existingLive.source&&existingLive.source!==source){
|
||||
try{existingLive.source.close();}catch(_){ }
|
||||
try{if(existingLive.source.readyState!==2)existingLive.source.close();}catch(_){ }
|
||||
}
|
||||
LIVE_STREAMS[activeSid]={streamId,source};
|
||||
|
||||
@@ -3672,7 +3672,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
if(typeof finalizeThinkingCard==='function') finalizeThinkingCard();
|
||||
// Application-level error sent explicitly by the server (rate limit, crash, etc.)
|
||||
// This is distinct from the SSE network 'error' event below.
|
||||
source.close();
|
||||
try{if(source&&source.readyState!==2)source.close();}catch(_){ }
|
||||
_clearOwnerInflightState();
|
||||
_clearApprovalForOwner();
|
||||
_clearClarifyForOwner('terminal');
|
||||
@@ -3782,7 +3782,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
return;
|
||||
}
|
||||
if(typeof recordClientSSEError==='function') recordClientSSEError('chat-response',{ready_state:source?source.readyState:null,session_id:activeSid,stream_id:streamId,reason:'chat EventSource.onerror'});
|
||||
source.close();
|
||||
try{if(source&&source.readyState!==2)source.close();}catch(_){ }
|
||||
if(_deferStreamErrorIfOffline()) return;
|
||||
if(_deferStreamErrorIfPageHidden(source)) return;
|
||||
_closeSource(source);
|
||||
@@ -3840,7 +3840,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
_streamFadeCleanupReduceMotionListener();
|
||||
_smdEndParser();
|
||||
if(typeof finalizeThinkingCard==='function') finalizeThinkingCard();
|
||||
source.close();
|
||||
try{if(source&&source.readyState!==2)source.close();}catch(_){ }
|
||||
_clearOwnerInflightState();
|
||||
_clearApprovalForOwner();
|
||||
_clearClarifyForOwner('cancelled');
|
||||
@@ -4445,7 +4445,7 @@ function stopApprovalPollingForSession(sid) {
|
||||
|
||||
function stopApprovalPolling() {
|
||||
if (_approvalPollTimer) { clearInterval(_approvalPollTimer); _approvalPollTimer = null; }
|
||||
if (_approvalEventSource) { try { _approvalEventSource.close(); } catch(_){} _approvalEventSource = null; }
|
||||
if (_approvalEventSource) { try { if(_approvalEventSource.readyState!==2)_approvalEventSource.close(); } catch(_){} _approvalEventSource = null; }
|
||||
if (_approvalSSEHealthTimer) { clearInterval(_approvalSSEHealthTimer); _approvalSSEHealthTimer = null; }
|
||||
_approvalFallbackPollInFlight = false;
|
||||
_approvalPollingSessionId = null;
|
||||
@@ -4581,7 +4581,7 @@ function startSessionStream(sid) {
|
||||
// it in the interim (stale onerror from a superseded stream), in
|
||||
// which case we must not stomp the live one.
|
||||
if (_sessionEventSource === es) {
|
||||
try { es.close(); } catch (_) {}
|
||||
try { if(es.readyState!==2)es.close(); } catch (_) {}
|
||||
_sessionEventSource = null;
|
||||
}
|
||||
_sessionStreamReconnectTimer = setTimeout(() => {
|
||||
@@ -4600,7 +4600,7 @@ function startSessionStream(sid) {
|
||||
function stopSessionStream() {
|
||||
if (_sessionStreamReconnectTimer) { clearTimeout(_sessionStreamReconnectTimer); _sessionStreamReconnectTimer = null; }
|
||||
if (_sessionEventSource) {
|
||||
try { _sessionEventSource.close(); } catch(_){}
|
||||
try { if(_sessionEventSource.readyState!==2)_sessionEventSource.close(); } catch(_){}
|
||||
_sessionEventSource = null;
|
||||
}
|
||||
_sessionStreamSessionId = null;
|
||||
@@ -5212,7 +5212,7 @@ function stopClarifyPollingForSession(sid) {
|
||||
}
|
||||
|
||||
function stopClarifyPolling() {
|
||||
if (_clarifyEventSource) { try { _clarifyEventSource.close(); } catch(_){} _clarifyEventSource = null; }
|
||||
if (_clarifyEventSource) { try { if(_clarifyEventSource.readyState!==2)_clarifyEventSource.close(); } catch(_){} _clarifyEventSource = null; }
|
||||
if (_clarifyFallbackTimer) { clearInterval(_clarifyFallbackTimer); _clarifyFallbackTimer = null; }
|
||||
if (_clarifyHealthTimer) { clearInterval(_clarifyHealthTimer); _clarifyHealthTimer = null; }
|
||||
_clarifyFallbackPollInFlight = false;
|
||||
|
||||
+2
-2
@@ -2044,13 +2044,13 @@ function _kanbanStartPolling(){
|
||||
|
||||
function _kanbanStopPolling(){
|
||||
if (_kanbanPollTimer) { clearInterval(_kanbanPollTimer); _kanbanPollTimer = null; }
|
||||
if (_kanbanEventSource) { try { _kanbanEventSource.close(); } catch(_) {} _kanbanEventSource = null; }
|
||||
if (_kanbanEventSource) { try { if(_kanbanEventSource.readyState!==2)_kanbanEventSource.close(); } catch(_) {} _kanbanEventSource = null; }
|
||||
}
|
||||
|
||||
function _kanbanStartEventStream(){
|
||||
// Tear down any prior stream before opening a new one (board switch,
|
||||
// login change, etc.).
|
||||
if (_kanbanEventSource) { try { _kanbanEventSource.close(); } catch(_) {} _kanbanEventSource = null; }
|
||||
if (_kanbanEventSource) { try { if(_kanbanEventSource.readyState!==2)_kanbanEventSource.close(); } catch(_) {} _kanbanEventSource = null; }
|
||||
const since = Number(_kanbanLatestEventId || 0);
|
||||
let url = '/api/kanban/events/stream' + _kanbanBoardQuery({since: since});
|
||||
let es;
|
||||
|
||||
+3
-3
@@ -3932,7 +3932,7 @@ function _installSidebarSseFocusHook(){
|
||||
|
||||
function _closeSessionEventsSSE(){
|
||||
if(_sessionEventsSSE){
|
||||
_sessionEventsSSE.close();
|
||||
try{if(_sessionEventsSSE.readyState!==2)_sessionEventsSSE.close();}catch(_){ }
|
||||
_sessionEventsSSE = null;
|
||||
}
|
||||
}
|
||||
@@ -4143,7 +4143,7 @@ function startGatewaySSE(){
|
||||
_gatewaySSE.onerror = () => {
|
||||
if(typeof recordClientSSEError==='function') recordClientSSEError('gateway-sessions',{ready_state:_gatewaySSE?_gatewaySSE.readyState:null,reason:'gateway EventSource.onerror'});
|
||||
if(_gatewaySSE){
|
||||
_gatewaySSE.close();
|
||||
try{if(_gatewaySSE.readyState!==2)_gatewaySSE.close();}catch(_){ }
|
||||
_gatewaySSE = null;
|
||||
}
|
||||
void probeGatewaySSEStatus();
|
||||
@@ -4155,7 +4155,7 @@ function startGatewaySSE(){
|
||||
|
||||
function stopGatewaySSE(){
|
||||
if(_gatewaySSE){
|
||||
_gatewaySSE.close();
|
||||
try{if(_gatewaySSE.readyState!==2)_gatewaySSE.close();}catch(_){ }
|
||||
_gatewaySSE = null;
|
||||
}
|
||||
stopGatewayPollFallback();
|
||||
|
||||
+6
-6
@@ -401,7 +401,7 @@ function _connectTerminalOutput(){
|
||||
const sid=_terminalSessionId();
|
||||
if(!sid)return;
|
||||
if(TERMINAL_UI.source){
|
||||
try{TERMINAL_UI.source.close();}catch(_){}
|
||||
try{if(TERMINAL_UI.source.readyState!==2)TERMINAL_UI.source.close();}catch(_){}
|
||||
TERMINAL_UI.source=null;
|
||||
}
|
||||
const url=new URL('api/terminal/output',document.baseURI||location.href);
|
||||
@@ -419,7 +419,7 @@ function _connectTerminalOutput(){
|
||||
source.addEventListener('terminal_closed',()=>{
|
||||
if(TERMINAL_UI.source!==source)return;
|
||||
if(TERMINAL_UI.term)TERMINAL_UI.term.writeln('\r\n[terminal closed]\r\n');
|
||||
try{source.close();}catch(_){}
|
||||
try{if(source&&source.readyState!==2)source.close();}catch(_){}
|
||||
TERMINAL_UI.source=null;
|
||||
setTimeout(()=>closeComposerTerminal(null,{skipApi:true}),260);
|
||||
});
|
||||
@@ -428,7 +428,7 @@ function _connectTerminalOutput(){
|
||||
let msg=t('terminal_error');
|
||||
try{msg=(JSON.parse(ev.data)||{}).error||msg;}catch(_){}
|
||||
if(TERMINAL_UI.term)TERMINAL_UI.term.writeln('\r\n[terminal error] '+msg+'\r\n');
|
||||
try{source.close();}catch(_){}
|
||||
try{if(source&&source.readyState!==2)source.close();}catch(_){}
|
||||
TERMINAL_UI.source=null;
|
||||
});
|
||||
}
|
||||
@@ -557,7 +557,7 @@ async function closeComposerTerminal(sessionId,opts){
|
||||
opts=opts||{};
|
||||
const sid=sessionId||TERMINAL_UI.sessionId||_terminalSessionId();
|
||||
if(TERMINAL_UI.source){
|
||||
try{TERMINAL_UI.source.close();}catch(_){}
|
||||
try{if(TERMINAL_UI.source&&TERMINAL_UI.source.readyState!==2)TERMINAL_UI.source.close();}catch(_){}
|
||||
TERMINAL_UI.source=null;
|
||||
}
|
||||
if(sid&&!opts.skipApi){
|
||||
@@ -588,7 +588,7 @@ async function closeComposerTerminal(sessionId,opts){
|
||||
async function restartComposerTerminal(){
|
||||
if(!TERMINAL_UI.open||TERMINAL_UI.collapsed)return;
|
||||
if(TERMINAL_UI.source){
|
||||
try{TERMINAL_UI.source.close();}catch(_){}
|
||||
try{if(TERMINAL_UI.source&&TERMINAL_UI.source.readyState!==2)TERMINAL_UI.source.close();}catch(_){}
|
||||
TERMINAL_UI.source=null;
|
||||
}
|
||||
if(TERMINAL_UI.term)TERMINAL_UI.term.reset();
|
||||
@@ -646,7 +646,7 @@ async function _resizeComposerTerminal(){
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload',()=>{
|
||||
if(TERMINAL_UI.source)try{TERMINAL_UI.source.close();}catch(_){}
|
||||
if(TERMINAL_UI.source)try{if(TERMINAL_UI.source&&TERMINAL_UI.source.readyState!==2)TERMINAL_UI.source.close();}catch(_){}
|
||||
if(TERMINAL_UI.sessionId){
|
||||
const url=new URL('api/terminal/close',document.baseURI||location.href).href;
|
||||
const body=JSON.stringify({session_id:TERMINAL_UI.sessionId});
|
||||
|
||||
Reference in new issue
Block a user