Merge pull request #1999 from code-yeongyu/fix/bug-3-6-15-tmux-deferred
fix(tmux-deferred): add TTL/max-size guards, null-state exit, and spawn atomicity
This commit is contained in:
@@ -36,6 +36,9 @@ const defaultTmuxDeps: TmuxUtilDeps = {
|
||||
getCurrentPaneId: defaultGetCurrentPaneId,
|
||||
}
|
||||
|
||||
const DEFERRED_SESSION_TTL_MS = 5 * 60 * 1000
|
||||
const MAX_DEFERRED_QUEUE_SIZE = 20
|
||||
|
||||
/**
|
||||
* State-first Tmux Session Manager
|
||||
*
|
||||
@@ -60,6 +63,7 @@ export class TmuxSessionManager {
|
||||
private deferredQueue: string[] = []
|
||||
private deferredAttachInterval?: ReturnType<typeof setInterval>
|
||||
private deferredAttachTickScheduled = false
|
||||
private nullStateCount = 0
|
||||
private deps: TmuxUtilDeps
|
||||
private pollingManager: TmuxPollingManager
|
||||
constructor(ctx: PluginInput, tmuxConfig: TmuxConfig, deps: TmuxUtilDeps = defaultTmuxDeps) {
|
||||
@@ -104,6 +108,14 @@ export class TmuxSessionManager {
|
||||
|
||||
private enqueueDeferredSession(sessionId: string, title: string): void {
|
||||
if (this.deferredSessions.has(sessionId)) return
|
||||
if (this.deferredQueue.length >= MAX_DEFERRED_QUEUE_SIZE) {
|
||||
log("[tmux-session-manager] deferred queue full, dropping session", {
|
||||
sessionId,
|
||||
queueLength: this.deferredQueue.length,
|
||||
maxQueueSize: MAX_DEFERRED_QUEUE_SIZE,
|
||||
})
|
||||
return
|
||||
}
|
||||
this.deferredSessions.set(sessionId, {
|
||||
sessionId,
|
||||
title,
|
||||
@@ -131,6 +143,7 @@ export class TmuxSessionManager {
|
||||
|
||||
private startDeferredAttachLoop(): void {
|
||||
if (this.deferredAttachInterval) return
|
||||
this.nullStateCount = 0
|
||||
this.deferredAttachInterval = setInterval(() => {
|
||||
if (this.deferredAttachTickScheduled) return
|
||||
this.deferredAttachTickScheduled = true
|
||||
@@ -152,6 +165,7 @@ export class TmuxSessionManager {
|
||||
clearInterval(this.deferredAttachInterval)
|
||||
this.deferredAttachInterval = undefined
|
||||
this.deferredAttachTickScheduled = false
|
||||
this.nullStateCount = 0
|
||||
log("[tmux-session-manager] deferred attach polling stopped")
|
||||
}
|
||||
|
||||
@@ -169,8 +183,36 @@ export class TmuxSessionManager {
|
||||
return
|
||||
}
|
||||
|
||||
if (Date.now() - deferred.queuedAt.getTime() > DEFERRED_SESSION_TTL_MS) {
|
||||
this.deferredQueue.shift()
|
||||
this.deferredSessions.delete(sessionId)
|
||||
log("[tmux-session-manager] deferred session expired", {
|
||||
sessionId,
|
||||
queuedAt: deferred.queuedAt.toISOString(),
|
||||
ttlMs: DEFERRED_SESSION_TTL_MS,
|
||||
queueLength: this.deferredQueue.length,
|
||||
})
|
||||
if (this.deferredQueue.length === 0) {
|
||||
this.stopDeferredAttachLoop()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const state = await queryWindowState(this.sourcePaneId)
|
||||
if (!state) return
|
||||
if (!state) {
|
||||
this.nullStateCount += 1
|
||||
log("[tmux-session-manager] deferred attach window state is null", {
|
||||
nullStateCount: this.nullStateCount,
|
||||
})
|
||||
if (this.nullStateCount >= 3) {
|
||||
log("[tmux-session-manager] stopping deferred attach loop after consecutive null states", {
|
||||
nullStateCount: this.nullStateCount,
|
||||
})
|
||||
this.stopDeferredAttachLoop()
|
||||
}
|
||||
return
|
||||
}
|
||||
this.nullStateCount = 0
|
||||
|
||||
const decision = decideSpawnActions(
|
||||
state,
|
||||
@@ -365,6 +407,10 @@ export class TmuxSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
const closeActionSucceeded = result.results.some(
|
||||
({ action, result: actionResult }) => action.type === "close" && actionResult.success,
|
||||
)
|
||||
|
||||
if (result.success && result.spawnedPaneId) {
|
||||
const sessionReady = await this.waitForSessionReady(sessionId)
|
||||
|
||||
@@ -399,6 +445,13 @@ export class TmuxSessionManager {
|
||||
})),
|
||||
})
|
||||
|
||||
if (closeActionSucceeded) {
|
||||
log("[tmux-session-manager] re-queueing deferred session after close+spawn failure", {
|
||||
sessionId,
|
||||
})
|
||||
this.enqueueDeferredSession(sessionId, title)
|
||||
}
|
||||
|
||||
if (result.spawnedPaneId) {
|
||||
await executeAction(
|
||||
{ type: "close", paneId: result.spawnedPaneId, sessionId },
|
||||
|
||||
Reference in New Issue
Block a user