From 15963d39eff2f6045b14db2a99e4e93f071631a3 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 23 Oct 2025 14:19:46 +0300 Subject: [PATCH] fix: primary session timeout promoting wrong session When primary timed out, emergency promotion was re-promoting the same timed-out session instead of promoting an observer. The emergency bypass logic ignored the excludeSessionID parameter. Fixed by applying session exclusion logic in all emergency promotion paths. --- jsonrpc.go | 3 --- session_cleanup_handlers.go | 42 ++++++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/jsonrpc.go b/jsonrpc.go index 6aa72263..14e3bcfa 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -179,9 +179,6 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { return } - // Update last active timestamp for any valid RPC activity - sessionManager.UpdateLastActive(session.ID) - var request JSONRPCRequest err := json.Unmarshal(message.Data, &request) if err != nil { diff --git a/session_cleanup_handlers.go b/session_cleanup_handlers.go index f79578b2..939c17bb 100644 --- a/session_cleanup_handlers.go +++ b/session_cleanup_handlers.go @@ -33,7 +33,27 @@ func (sm *SessionManager) attemptEmergencyPromotion(ctx emergencyPromotionContex Str("triggerSessionID", ctx.triggerSessionID). Str("triggerReason", ctx.triggerReason). Msg("Bypassing emergency promotion rate limits - no primary exists") - promotedSessionID := sm.findMostTrustedSessionForEmergency() + + // Find best session, excluding the specified session if provided + var promotedSessionID string + if excludeSessionID != "" { + bestSessionID := "" + bestScore := -1 + for id, session := range sm.sessions { + if id != excludeSessionID && + !sm.isSessionBlacklisted(id) && + (session.Mode == SessionModeObserver || session.Mode == SessionModeQueued) { + score := sm.getSessionTrustScore(id) + if score > bestScore { + bestScore = score + bestSessionID = id + } + } + } + promotedSessionID = bestSessionID + } else { + promotedSessionID = sm.findMostTrustedSessionForEmergency() + } return promotedSessionID, true, false } @@ -266,6 +286,12 @@ func (sm *SessionManager) handlePrimarySessionTimeout(now time.Time) bool { primary.Mode = SessionModeObserver sm.primarySessionID = "" + sm.logger.Info(). + Str("sessionID", timedOutSessionID). + Dur("inactiveFor", now.Sub(primary.LastActive)). + Dur("timeout", currentTimeout). + Msg("Primary session timed out due to inactivity - demoted to observer") + ctx := emergencyPromotionContext{ triggerSessionID: timedOutSessionID, triggerReason: "timeout", @@ -274,7 +300,8 @@ func (sm *SessionManager) handlePrimarySessionTimeout(now time.Time) bool { promotedSessionID, isEmergency, shouldSkip := sm.attemptEmergencyPromotion(ctx, timedOutSessionID) if shouldSkip { - return false + sm.logger.Info().Msg("Promotion skipped after timeout - session demoted but no promotion") + return true // Still need to broadcast the demotion } if promotedSessionID != "" { @@ -307,8 +334,17 @@ func (sm *SessionManager) handlePrimarySessionTimeout(now time.Time) bool { Bool("isEmergencyPromotion", isEmergency). Msg("Auto-promoted session after primary timeout") return true + } else { + sm.logger.Error().Err(err). + Str("timedOutSessionID", timedOutSessionID). + Str("promotedSessionID", promotedSessionID). + Msg("Failed to promote session after timeout - primary demoted") + return true // Still broadcast the demotion even if promotion failed } } - return false + sm.logger.Info(). + Str("timedOutSessionID", timedOutSessionID). + Msg("Primary session timed out - demoted to observer, no eligible sessions to promote") + return true // Broadcast the demotion even if no promotion }