fix(folder): prevent stale file watch subscriptions in aux tabs

Ensure async watch setup is safely discarded after effect cleanup.

Use idempotent watcher release logic to avoid duplicate subscriptions and unbalanced stop calls.
This commit is contained in:
xintaofei
2026-04-14 16:43:35 +08:00
parent 9f1540129b
commit 90e8bb645a
2 changed files with 58 additions and 6 deletions

View File

@@ -1895,8 +1895,23 @@ export function FileTreeTab() {
if (!rootPath) return
let unlisten: (() => void) | null = null
let disposed = false
let watchStarted = false
let watchReleased = false
const normalizedRootPath = normalizeComparePath(rootPath)
const releaseWatch = () => {
if (watchReleased) return
watchReleased = true
if (unlisten) {
unlisten()
unlisten = null
}
if (watchStarted) {
void stopFileTreeWatch(rootPath)
}
}
const scheduleTreeRefresh = (refreshGitStatus: boolean) => {
if (!isFileTreeTabActiveRef.current) {
pendingTreeRefreshRef.current = true
@@ -2027,13 +2042,18 @@ export function FileTreeTab() {
const setup = async () => {
try {
await startFileTreeWatch(rootPath)
watchStarted = true
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
toast.error(t("toasts.watchStartFailed"), { description: message })
}
if (disposed) {
releaseWatch()
return
}
try {
unlisten = await subscribe<FileTreeChangedEvent>(
const subscribedUnlisten = await subscribe<FileTreeChangedEvent>(
"folder://file-tree-changed",
(payload) => {
if (
@@ -2109,6 +2129,12 @@ export function FileTreeTab() {
})()
}
)
if (disposed) {
subscribedUnlisten()
releaseWatch()
return
}
unlisten = subscribedUnlisten
} catch (error) {
console.error("[FileTreeTab] failed to listen file watch event:", error)
}
@@ -2117,6 +2143,7 @@ export function FileTreeTab() {
void setup()
return () => {
disposed = true
if (treeRefreshTimerRef.current) {
clearTimeout(treeRefreshTimerRef.current)
treeRefreshTimerRef.current = null
@@ -2129,8 +2156,7 @@ export function FileTreeTab() {
pendingTreeRefreshRef.current = false
pendingTreeRefreshNeedsStatusRef.current = false
pendingStatusRefreshRef.current = false
unlisten?.()
void stopFileTreeWatch(rootPath)
releaseWatch()
}
}, [fetchTree, folder?.path, openFilePreview, t])

View File

@@ -615,8 +615,23 @@ export function GitChangesTab() {
if (!rootPath || !isChangesTabActive) return
let unlisten: (() => void) | null = null
let disposed = false
let watchStarted = false
let watchReleased = false
const normalizedRootPath = normalizeComparePath(rootPath)
const releaseWatch = () => {
if (watchReleased) return
watchReleased = true
if (unlisten) {
unlisten()
unlisten = null
}
if (watchStarted) {
void stopFileTreeWatch(rootPath)
}
}
const scheduleRefresh = () => {
if (refreshTimerRef.current) {
clearTimeout(refreshTimerRef.current)
@@ -629,12 +644,17 @@ export function GitChangesTab() {
const setup = async () => {
try {
await startFileTreeWatch(rootPath)
watchStarted = true
} catch {
// ignore watch startup errors
}
if (disposed) {
releaseWatch()
return
}
try {
unlisten = await subscribe<FileTreeChangedEvent>(
const subscribedUnlisten = await subscribe<FileTreeChangedEvent>(
"folder://file-tree-changed",
(payload) => {
if (
@@ -646,6 +666,12 @@ export function GitChangesTab() {
scheduleRefresh()
}
)
if (disposed) {
subscribedUnlisten()
releaseWatch()
return
}
unlisten = subscribedUnlisten
} catch {
// ignore listen errors
}
@@ -654,12 +680,12 @@ export function GitChangesTab() {
void setup()
return () => {
disposed = true
if (refreshTimerRef.current) {
clearTimeout(refreshTimerRef.current)
refreshTimerRef.current = null
}
unlisten?.()
void stopFileTreeWatch(rootPath)
releaseWatch()
}
}, [fetchChanges, folder?.path, isChangesTabActive])