fix(tasks): close background task review findings

This commit is contained in:
Yichen Jiang
2026-07-13 16:29:50 +08:00
parent e2bc79a0ef
commit 351a2e42b3
14 changed files with 78 additions and 25 deletions

View File

@@ -83,6 +83,8 @@ interface TrackedTask {
markSettled: () => void
/** Live {@link TaskService.wait} calls — a settlement with waiters marks the task reported. */
waiters: number
/** Removable resolvers for live waits; timeout/abort unregister before the task settles. */
waitResolvers: Set<() => void>
}
/** True for the three terminal {@link TaskStatus} values. */
@@ -169,6 +171,7 @@ export class TaskService extends Service {
settled,
markSettled,
waiters: 0,
waitResolvers: new Set(),
}
this.store.set(id, task)
@@ -276,8 +279,10 @@ export class TaskService extends Service {
* has already settled: settlement saw this live waiter and suppressed the
* completion notice on its behalf, so the wait still resolves and delivers
* the terminal snapshot it owes (an abort must never leave a finished task
* both unreported and notice-suppressed). Throws for an unknown id, a task
* owned by another session, or a non-positive timeout.
* both unreported and notice-suppressed). Each live wait uses a removable
* resolver that timeout/abort detaches, so a long-running task does not
* retain expired waits. Throws for an unknown id, a task owned by another
* session, or a non-positive timeout.
* @param id - the task to wait for.
* @param timeoutMs - max wait in milliseconds (positive, finite; the surface caps it).
* @param caller - the waiting agent, checked against the task's owner.
@@ -313,7 +318,13 @@ export class TaskService extends Service {
// the wait. `using` clears the timer on every exit path.
using d = deadline(signal, timeoutMs, TASK_WAIT_TIMEOUT)
await new Promise<void>((resolve, reject) => {
const onSettled = (): void => {
task.waitResolvers.delete(onSettled)
d.signal.removeEventListener('abort', onAbort)
resolve()
}
const onAbort = (): void => {
task.waitResolvers.delete(onSettled)
if (timeoutOf(d.signal, TASK_WAIT_TIMEOUT) !== undefined) {
resolve()
} else if (isTerminal(task.status)) {
@@ -325,11 +336,8 @@ export class TaskService extends Service {
reject(new Error('wait aborted'))
}
}
task.waitResolvers.add(onSettled)
d.signal.addEventListener('abort', onAbort, { once: true })
void task.settled.then(() => {
d.signal.removeEventListener('abort', onAbort)
resolve()
})
})
} finally {
uncount()
@@ -437,6 +445,9 @@ export class TaskService extends Service {
}
}
}
const waitResolvers = [...task.waitResolvers]
task.waitResolvers.clear()
for (const resolveWait of waitResolvers) resolveWait()
task.markSettled()
}