mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
* Move local usage services into a supervised backend process * Own usage recovery and worker retirement lifetimes * Preserve answered usage data during background refresh * Run independent usage scans concurrently in the backend * Limit backend startup paths to the delegated usage service * Show usage failures through backend recovery * Include usage failure strings in the English catalog * Keep usage update details beside refresh failures * Resolve usage freshness failure from the final producer snapshot * Retain the forced refresh completion requirement * Keep managed backend failures out of application crash reports * Preserve usage recovery boundaries across backend reconnects * Match the backend RPC cancellation signal in the usage seam proof * Avoid serializing backend requests before port delivery * Keep receiving request validation in the boundary schemas * test: align usage transport coverage with renderer project * fix: preserve unknown usage state and renderer retirement * fix(usage): revalidate tracking before scan admission * fix(usage): retry failed backend startup on demand * test(usage): isolate stale backend ownership checks
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { startOrcadEventLoopWatchdog } from '../orcad/orcad-event-loop-watchdog'
|
|
|
|
export type BackendServiceCleanup = (state: { failed: boolean }) => void | Promise<void>
|
|
|
|
/** A service keeps its ownership fence until all accepted work and writers have retired. */
|
|
export class BackendServiceLifetime {
|
|
private readonly cleanups: BackendServiceCleanup[] = []
|
|
private stopping?: Promise<void>
|
|
|
|
constructor(private readonly release: () => void = () => {}) {}
|
|
|
|
add(cleanup: BackendServiceCleanup): void {
|
|
if (this.stopping) {
|
|
throw new Error('backend_service_lifetime_stopping')
|
|
}
|
|
this.cleanups.push(cleanup)
|
|
}
|
|
|
|
stop(): Promise<void> {
|
|
this.stopping ??= Promise.resolve().then(async () => {
|
|
const errors: unknown[] = []
|
|
for (const cleanup of this.cleanups.splice(0).toReversed()) {
|
|
try {
|
|
await cleanup({ failed: errors.length > 0 })
|
|
} catch (error) {
|
|
errors.push(error)
|
|
}
|
|
}
|
|
if (errors.length === 1) {
|
|
throw errors[0]
|
|
}
|
|
if (errors.length > 1) {
|
|
throw new AggregateError(errors, 'backend_service_cleanup_failed')
|
|
}
|
|
this.release()
|
|
})
|
|
return this.stopping
|
|
}
|
|
}
|
|
|
|
/** Both hosts enter service activation here without importing an inactive service set. */
|
|
export async function activateBackendServices<T>(options: {
|
|
supervised: boolean
|
|
registerCleanup: (cleanup: BackendServiceCleanup) => void
|
|
activate: (registerCleanup: (cleanup: BackendServiceCleanup) => void) => Promise<T>
|
|
}): Promise<T> {
|
|
const watchdog = startOrcadEventLoopWatchdog({ supervised: options.supervised })
|
|
options.registerCleanup(() => watchdog?.stop())
|
|
return options.activate(options.registerCleanup)
|
|
}
|