kimi-code / packages /agent-core-v2 /src /runtime /runtimeUnitHost.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw
History Blame Contribute Delete
16.5 kB
import { SyncDescriptor } from '#/_base/di/descriptors';
import { _util, type IInstantiationService, type ServiceIdentifier } from '#/_base/di/instantiation';
import { ServiceCollection } from '#/_base/di/serviceCollection';
import type { Runtime } from './runtime';
import type { RuntimeRegistrationHandle, RuntimeRegistry } from './runtimeRegistry';
type RuntimeUnitConstructor<T> = new (...args: never[]) => T;
export interface RuntimeUnitImports {
readonly root: readonly ServiceIdentifier<unknown>[];
readonly imports: readonly ServiceIdentifier<unknown>[];
readonly local: readonly ServiceIdentifier<unknown>[];
}
export interface RuntimeProviderRuntimeHandle {
readonly runtimeId: string;
update(prepare: () => Runtime | Promise<Runtime>): Promise<void>;
remove(): Promise<void>;
}
export interface RuntimeProviderHost {
get<T>(id: ServiceIdentifier<T>): T;
provide<T>(id: ServiceIdentifier<T>, ctor: RuntimeUnitConstructor<T>, ...staticArguments: unknown[]): T;
registerRuntime(runtime: Runtime): RuntimeProviderRuntimeHandle;
}
export interface RuntimeUnitHandle {
update<T extends { dispose(): void | Promise<void> }>(
imports: RuntimeUnitImports,
prepare: (host: RuntimeProviderHost) => Promise<T>,
): Promise<void>;
remove(): Promise<void>;
dispose(): Promise<void>;
}
export interface RuntimeUnitHost {
provide<T extends { dispose(): void | Promise<void> }>(
imports: RuntimeUnitImports,
prepare: (host: RuntimeProviderHost) => Promise<T>,
): Promise<RuntimeUnitHandle>;
update<T extends { dispose(): void | Promise<void> }>(
handle: RuntimeUnitHandle,
imports: RuntimeUnitImports,
prepare: (host: RuntimeProviderHost) => Promise<T>,
): Promise<void>;
remove(handle: RuntimeUnitHandle): Promise<void>;
dispose(): Promise<void>;
}
export interface RuntimeUnitHostFactory {
create(root: IInstantiationService, registry: RuntimeRegistry): RuntimeUnitHost;
}
export class SharedRuntimeUnitHostFactory implements RuntimeUnitHostFactory {
create(root: IInstantiationService, registry: RuntimeRegistry): RuntimeUnitHost {
return new SharedRuntimeUnitHost(root, registry);
}
}
interface LocalRegistration {
readonly id: ServiceIdentifier<unknown>;
readonly value: unknown;
}
interface RuntimeUnitTransaction {
readonly host: RuntimeProviderHost;
readonly units: Array<{ dispose(): void | Promise<void> }>;
readonly local: LocalRegistration[];
readonly runtimes: StagedRuntime[];
dispose(): Promise<void>;
commit(): { readonly cleanup: Promise<void> };
}
interface StagedRuntime {
runtime: Runtime;
registration?: RuntimeRegistrationHandle;
active: boolean;
}
interface RuntimeUnitRecord {
attachment: { dispose(): void | Promise<void> };
transaction: RuntimeUnitTransaction;
active: boolean;
handle?: RuntimeUnitHandle;
}
class SharedRuntimeUnitHost implements RuntimeUnitHost {
private readonly records: RuntimeUnitRecord[] = [];
private readonly recordByHandle = new Map<RuntimeUnitHandle, RuntimeUnitRecord>();
private readonly locals = new Map<ServiceIdentifier<unknown>, LocalRegistration>();
private tail = Promise.resolve();
private closing = false;
constructor(private readonly root: IInstantiationService, private readonly registry: RuntimeRegistry) {}
provide<T extends { dispose(): void | Promise<void> }>(
imports: RuntimeUnitImports,
prepare: (host: RuntimeProviderHost) => Promise<T>,
): Promise<RuntimeUnitHandle> {
if (this.closing) return Promise.reject(new Error('runtime unit host is disposed'));
return this.enqueue(async () => {
this.assertOpen();
const transaction = this.createTransaction(imports);
let attachment: T;
let cleanup: Promise<void>;
try {
attachment = await prepare(transaction.host);
cleanup = transaction.commit().cleanup;
} catch (error) {
await transaction.dispose();
throw error;
}
const record: RuntimeUnitRecord = { attachment, transaction, active: true };
const handle = this.handle(record);
record.handle = handle;
this.records.push(record);
this.recordByHandle.set(handle, record);
await cleanup;
return handle;
});
}
update<T extends { dispose(): void | Promise<void> }>(
handle: RuntimeUnitHandle,
imports: RuntimeUnitImports,
prepare: (host: RuntimeProviderHost) => Promise<T>,
): Promise<void> {
if (this.closing) return Promise.reject(new Error('runtime unit host is disposed'));
return this.enqueue(async () => {
this.assertOpen();
const record = this.find(handle);
if (!record.active) throw new Error('runtime unit handle is disposed');
const transaction = this.createTransaction(imports, record.transaction);
let attachment: T;
let cleanup: Promise<void>;
try {
attachment = await prepare(transaction.host);
cleanup = transaction.commit().cleanup;
} catch (error) {
await transaction.dispose();
throw error;
}
const previousAttachment = record.attachment;
const previousTransaction = record.transaction;
record.attachment = attachment;
record.transaction = transaction;
let failure: unknown;
let failed = false;
try {
await cleanup;
} catch (error) {
failure = error;
failed = true;
}
try {
await previousAttachment.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
try {
await previousTransaction.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
if (failed) throw failure;
});
}
remove(handle: RuntimeUnitHandle): Promise<void> {
return this.enqueue(async () => {
const record = this.find(handle);
if (!record.active) return;
record.active = false;
let failure: unknown;
let failed = false;
try {
await record.attachment.dispose();
} catch (error) {
failure = error;
failed = true;
}
try {
await record.transaction.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
const index = this.records.indexOf(record);
if (index >= 0) this.records.splice(index, 1);
this.recordByHandle.delete(handle);
if (failed) throw failure;
});
}
async dispose(): Promise<void> {
if (this.closing) return this.tail;
this.closing = true;
await this.tail;
await this.enqueue(async () => {
let failure: unknown;
let failed = false;
for (const record of [...this.records].reverse()) {
if (!record.active) continue;
record.active = false;
try {
await record.attachment.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
try {
await record.transaction.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
if (record.handle !== undefined) this.recordByHandle.delete(record.handle);
}
this.records.length = 0;
if (failed) throw failure;
});
await this.tail;
}
private handle(_record: RuntimeUnitRecord): RuntimeUnitHandle {
const handle: RuntimeUnitHandle = {
update: (imports, prepare) => this.update(handle, imports, prepare),
remove: () => this.remove(handle),
dispose: () => this.remove(handle),
};
return handle;
}
private find(handle: RuntimeUnitHandle): RuntimeUnitRecord {
const record = this.recordByHandle.get(handle);
if (record === undefined) throw new Error('runtime unit handle is not owned by this host');
return record;
}
private enqueue<T>(work: () => Promise<T>): Promise<T> {
const next = this.tail.then(work, work);
this.tail = next.then(() => {}, () => {});
return next;
}
private assertOpen(): void {
if (this.closing) throw new Error('runtime unit host is disposed');
}
private createTransaction(imports: RuntimeUnitImports, previous?: RuntimeUnitTransaction): RuntimeUnitTransaction {
const declared = new Set([...imports.root, ...imports.imports, ...imports.local]);
if (declared.size !== imports.root.length + imports.imports.length + imports.local.length) {
throw new Error('runtime unit dependency manifest contains duplicate declarations');
}
const services = new ServiceCollection();
const units: Array<{ dispose(): void | Promise<void> }> = [];
const local: LocalRegistration[] = [];
const runtimes: StagedRuntime[] = [];
let active = true;
let committed = false;
for (const id of imports.root) {
services.set(id, this.root.invokeFunction((accessor) => accessor.get(id)));
}
for (const id of imports.imports) {
const registration = this.locals.get(id);
if (registration === undefined) throw new Error(`runtime unit import is not available ${id.toString()}`);
services.set(id, registration.value);
}
const child = this.root.createChild(services);
const host: RuntimeProviderHost = {
get: <T>(id: ServiceIdentifier<T>): T => {
if (!active || !declared.has(id)) throw new Error(`runtime unit dependency is not declared ${id.toString()}`);
if (imports.local.includes(id) && !local.some((registration) => registration.id === id)) {
throw new Error(`runtime unit local dependency is not available ${id.toString()}`);
}
return child.invokeFunction((accessor) => accessor.get(id));
},
provide: <T>(id: ServiceIdentifier<T>, ctor: RuntimeUnitConstructor<T>, ...staticArguments: unknown[]): T => {
if (!active || !imports.local.includes(id)) throw new Error(`runtime unit local registration is not declared ${id.toString()}`);
if (local.some((registration) => registration.id === id)) throw new Error(`runtime unit local registration already exists ${id.toString()}`);
for (const dependency of _util.getInstanceDependencies(ctor as unknown as _util.DI_TARGET_OBJ)) {
if (!declared.has(dependency.id)) throw new Error(`runtime unit dependency is not declared ${dependency.id.toString()}`);
if (imports.local.includes(dependency.id) && !local.some((registration) => registration.id === dependency.id)) {
throw new Error(`runtime unit local dependency is not available ${dependency.id.toString()}`);
}
}
const unit = child.createInstance(new SyncDescriptor<T>(ctor as never, staticArguments)) as T;
services.set(id, unit);
local.push({ id, value: unit });
const disposable = unit as { dispose?: () => void | Promise<void> };
if (typeof disposable.dispose === 'function') units.push(disposable as { dispose(): void | Promise<void> });
return unit;
},
registerRuntime: (runtime) => {
if (!active) throw new Error('runtime unit transaction is disposed');
if (runtimes.some((entry) => entry.runtime.identity.runtimeId === runtime.identity.runtimeId)) {
throw new Error(`runtime ${runtime.identity.runtimeId} is registered twice in one transaction`);
}
const staged: StagedRuntime = { runtime, active: true };
if (committed) staged.registration = this.registry.register(runtime);
runtimes.push(staged);
const handle: RuntimeProviderRuntimeHandle = {
runtimeId: runtime.identity.runtimeId,
update: (replacement) => this.updateRuntime(staged, replacement),
remove: async () => {
try {
await this.removeRuntime(staged);
} finally {
const index = runtimes.indexOf(staged);
if (index >= 0) runtimes.splice(index, 1);
}
},
};
return handle;
},
};
const transaction: RuntimeUnitTransaction = {
host,
units,
local,
runtimes,
commit: () => {
if (!active) throw new Error('runtime unit transaction is disposed');
const previousRuntimes = new Map(
previous?.runtimes.map((staged) => [staged.runtime.identity.runtimeId, staged]) ?? [],
);
const previousLocals = new Set(previous?.local.map((registration) => registration.id) ?? []);
for (const staged of runtimes) {
const current = this.registry.current(staged.runtime.identity.runtimeId);
const previousRuntime = previousRuntimes.get(staged.runtime.identity.runtimeId);
if (current !== undefined && previousRuntime === undefined) {
throw new Error(`runtime ${staged.runtime.identity.runtimeId} already exists`);
}
this.registry.prepare(
staged.runtime,
previousRuntime === undefined ? undefined : staged.runtime.identity.runtimeId,
);
}
for (const registration of local) {
if (this.locals.has(registration.id) && !previousLocals.has(registration.id)) {
throw new Error(`runtime unit local registration already exists ${registration.id.toString()}`);
}
}
const publication = this.registry.publishBatch(runtimes.map((staged) => {
const previousRuntime = previousRuntimes.get(staged.runtime.identity.runtimeId);
if (previousRuntime?.registration === undefined) return { runtime: staged.runtime };
return {
runtime: staged.runtime,
current: previousRuntime.runtime,
registration: previousRuntime.registration,
};
}));
for (let index = 0; index < runtimes.length; index += 1) {
const staged = runtimes[index]!;
const previousRuntime = previousRuntimes.get(staged.runtime.identity.runtimeId);
if (previousRuntime !== undefined) previousRuntime.active = false;
staged.registration = publication.registrations[index];
}
for (const registration of local) this.locals.set(registration.id, registration);
committed = true;
return { cleanup: publication.cleanup };
},
dispose: async () => {
if (!active) return;
active = false;
let failure: unknown;
let failed = false;
for (const staged of runtimes.reverse()) {
if (!staged.active) continue;
staged.active = false;
try {
if (staged.registration === undefined) await staged.runtime.dispose();
else await staged.registration.remove();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
}
for (const registration of local.reverse()) {
if (this.locals.get(registration.id) === registration) this.locals.delete(registration.id);
}
for (const unit of units.reverse()) {
try {
await unit.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
}
try {
child.dispose();
} catch (error) {
if (!failed) failure = error;
failed = true;
}
if (failed) throw failure;
},
};
return transaction;
}
private updateRuntime(staged: StagedRuntime, prepare: () => Runtime | Promise<Runtime>): Promise<void> {
if (this.closing) return Promise.reject(new Error('runtime unit host is disposed'));
return this.enqueue(async () => {
if (!staged.active || staged.registration === undefined) throw new Error('runtime registration is not active');
const replacement = await prepare();
let cleanup: Promise<void>;
try {
this.registry.prepare(replacement, staged.runtime.identity.runtimeId);
cleanup = this.registry.publishBatch([{
runtime: replacement,
current: staged.runtime,
registration: staged.registration,
}]).cleanup;
} catch (error) {
await replacement.dispose();
throw error;
}
staged.runtime = replacement;
await cleanup;
});
}
private async removeRuntime(staged: StagedRuntime): Promise<void> {
if (!staged.active) return;
staged.active = false;
if (staged.registration === undefined) await staged.runtime.dispose();
else await staged.registration.remove();
}
}