Files
orca/src/main/backend/service-composition.ts
T
Brennan Benson 175f10e32b Run local usage services in a supervised backend process (#40792)
* 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
2026-11-11 16:39:52 -08:00

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)
}