fix: reduce observer promotion delay from ~40s to ~11s

1. Terminal access permission check:
   - Add Permission.TERMINAL_ACCESS check to Web Terminal button
   - Prevents observer sessions from accessing terminal

2. Immediate websocket cleanup:
   - Close peer connection immediately when websocket errors
   - Previously waited 24+ seconds for ICE to transition from disconnected to failed
   - Now triggers session cleanup immediately on tab close

3. Immediate grace period validation:
   - Trigger validateSinglePrimary() immediately when grace period expires
   - Previously waited up to 10 seconds for next periodic validation
   - Eliminates unnecessary delay in observer promotion

Timeline improvement:
Before: Tab close → 6s (ICE disconnect) → 24s (ICE fail) → RemoveSession → 10s grace → up to 10s validation = ~50s total
After: Tab close → immediate peerConnection.Close() → immediate RemoveSession → 10s grace → immediate validation = ~11s total
This commit is contained in:
Alex P 2025-10-09 11:39:00 +03:00
parent 7901677551
commit b388bc3c62
3 changed files with 21 additions and 6 deletions

View File

@ -1520,9 +1520,11 @@ func (sm *SessionManager) cleanupInactiveSessions(ctx context.Context) {
needsBroadcast := false needsBroadcast := false
// Check for expired grace periods and promote if needed // Check for expired grace periods and promote if needed
gracePeriodExpired := false
for sessionID, graceTime := range sm.reconnectGrace { for sessionID, graceTime := range sm.reconnectGrace {
if now.After(graceTime) { if now.After(graceTime) {
delete(sm.reconnectGrace, sessionID) delete(sm.reconnectGrace, sessionID)
gracePeriodExpired = true
wasHoldingPrimarySlot := (sm.lastPrimaryID == sessionID) wasHoldingPrimarySlot := (sm.lastPrimaryID == sessionID)
@ -1735,12 +1737,18 @@ func (sm *SessionManager) cleanupInactiveSessions(ctx context.Context) {
} }
} }
// Periodic validateSinglePrimary to catch deadlock states // Run validation immediately if a grace period expired, otherwise run periodically
validationCounter++ if gracePeriodExpired {
if validationCounter >= 10 { // Every 10 seconds sm.logger.Debug().Msg("Running immediate validation after grace period expiration")
validationCounter = 0
sm.logger.Debug().Msg("Running periodic session validation to catch deadlock states")
sm.validateSinglePrimary() sm.validateSinglePrimary()
} else {
// Periodic validateSinglePrimary to catch deadlock states
validationCounter++
if validationCounter >= 10 { // Every 10 seconds
validationCounter = 0
sm.logger.Debug().Msg("Running periodic session validation to catch deadlock states")
sm.validateSinglePrimary()
}
} }
sm.mu.Unlock() sm.mu.Unlock()

View File

@ -93,7 +93,7 @@ export default function Actionbar({
className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2 py-1.5" className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2 py-1.5"
> >
<div className="relative flex flex-wrap items-center gap-x-2 gap-y-2"> <div className="relative flex flex-wrap items-center gap-x-2 gap-y-2">
{developerMode && ( {developerMode && hasPermission(Permission.TERMINAL_ACCESS) && (
<Button <Button
size="XS" size="XS"
theme="light" theme="light"

7
web.go
View File

@ -357,6 +357,13 @@ func handleWebRTCSignalWsMessages(
typ, msg, err := wsCon.Read(runCtx) typ, msg, err := wsCon.Read(runCtx)
if err != nil { if err != nil {
l.Warn().Str("error", err.Error()).Msg("websocket read error") l.Warn().Str("error", err.Error()).Msg("websocket read error")
// Clean up session when websocket closes
if session := sessionManager.GetSession(connectionID); session != nil && session.peerConnection != nil {
l.Info().
Str("sessionID", session.ID).
Msg("Closing peer connection due to websocket error")
_ = session.peerConnection.Close()
}
return err return err
} }
if typ != websocket.MessageText { if typ != websocket.MessageText {