refactor(desktop): session-scoped voice runtime, STT/LLM ports, caption ownership and meeting export fixes
This commit is contained in:
parent
0adedf5e7b
commit
83cf9133ac
39 changed files with 4787 additions and 1061 deletions
113
apps/desktop/src/main/shutdown-registry.ts
Normal file
113
apps/desktop/src/main/shutdown-registry.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// src/main/shutdown-registry.ts
|
||||
// 앱 종료 시 정리할 자원(사이드카 프로세스, 전역 입력 훅, 타이머 등)의 등록부.
|
||||
//
|
||||
// 예전엔 lifecycle.ts 가 disposeLocalLLMService 하나만 하드코딩해 호출했고, 새로 프로세스를
|
||||
// 소유하는 서비스(STT 사이드카 등)는 lifecycle 을 고치는 것을 잊어 종료 후에도 고아로 남았다.
|
||||
// 이제 서비스는 시작 시점에 스스로 등록하고, lifecycle 은 이 등록부에만 의존한다(DIP/OCP).
|
||||
|
||||
export interface Disposable {
|
||||
name: string
|
||||
dispose(): Promise<void> | void
|
||||
}
|
||||
|
||||
export interface ShutdownReport {
|
||||
completed: string[]
|
||||
failed: string[]
|
||||
timedOut: string[]
|
||||
}
|
||||
|
||||
export interface ShutdownRegistryOptions {
|
||||
now?: () => number
|
||||
onError?: (name: string, error: unknown) => void
|
||||
}
|
||||
|
||||
const TIMED_OUT = Symbol('timed-out')
|
||||
|
||||
export class ShutdownRegistry {
|
||||
private _entries: Disposable[] = []
|
||||
private _running: Promise<ShutdownReport> | null = null
|
||||
private readonly _now: () => number
|
||||
private readonly _onError: (name: string, error: unknown) => void
|
||||
|
||||
constructor(options: ShutdownRegistryOptions = {}) {
|
||||
this._now = options.now ?? (() => Date.now())
|
||||
this._onError = options.onError ?? (() => undefined)
|
||||
}
|
||||
|
||||
/** 정리 대상을 등록한다. 돌려받은 함수로 등록을 취소할 수 있다. */
|
||||
register(entry: Disposable): () => void {
|
||||
this._entries.push(entry)
|
||||
return () => {
|
||||
this._entries = this._entries.filter((e) => e !== entry)
|
||||
}
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._entries.length
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록의 역순(나중에 시작한 것부터)으로 하나씩 정리한다. 여러 번 불러도 한 번만 실행된다.
|
||||
* 전체 시간 상한(timeoutMs)을 넘기면 남은 항목은 기다리지 않고 dispose 만 걸어 둔다
|
||||
* (동기 구간 — 프로세스 kill 등 — 은 그래도 실행된다). 한 항목이 멈춰도 종료가 막히지 않는다.
|
||||
*/
|
||||
disposeAll(timeoutMs: number): Promise<ShutdownReport> {
|
||||
if (!this._running) this._running = this._run(timeoutMs)
|
||||
return this._running
|
||||
}
|
||||
|
||||
private async _run(timeoutMs: number): Promise<ShutdownReport> {
|
||||
const report: ShutdownReport = { completed: [], failed: [], timedOut: [] }
|
||||
const deadline = this._now() + timeoutMs
|
||||
const entries = [...this._entries].reverse()
|
||||
|
||||
for (const entry of entries) {
|
||||
const remaining = deadline - this._now()
|
||||
let pending: Promise<void>
|
||||
try {
|
||||
pending = Promise.resolve(entry.dispose())
|
||||
} catch (error) {
|
||||
this._onError(entry.name, error)
|
||||
report.failed.push(entry.name)
|
||||
continue
|
||||
}
|
||||
|
||||
if (remaining <= 0) {
|
||||
pending.catch((error: unknown) => this._onError(entry.name, error))
|
||||
report.timedOut.push(entry.name)
|
||||
continue
|
||||
}
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
const timeout = new Promise<typeof TIMED_OUT>((resolve) => {
|
||||
timer = setTimeout(() => resolve(TIMED_OUT), remaining)
|
||||
})
|
||||
try {
|
||||
const outcome = await Promise.race([pending.then(() => 'done' as const), timeout])
|
||||
if (outcome === TIMED_OUT) {
|
||||
pending.catch((error: unknown) => this._onError(entry.name, error))
|
||||
report.timedOut.push(entry.name)
|
||||
} else {
|
||||
report.completed.push(entry.name)
|
||||
}
|
||||
} catch (error) {
|
||||
this._onError(entry.name, error)
|
||||
report.failed.push(entry.name)
|
||||
} finally {
|
||||
if (timer) clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
return report
|
||||
}
|
||||
}
|
||||
|
||||
let instance: ShutdownRegistry | null = null
|
||||
|
||||
export function getShutdownRegistry(): ShutdownRegistry {
|
||||
if (!instance) instance = new ShutdownRegistry()
|
||||
return instance
|
||||
}
|
||||
|
||||
export function resetShutdownRegistryForTests(): void {
|
||||
instance = null
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue