mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:17:47 +08:00
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import type { WorkerRecord } from '../domain/worker';
|
|
import type { WorkerExecutionOfferJournalOwnership } from '../ports/workerExecutionOfferJournalOwnership';
|
|
import type {
|
|
WorkerExecutionOfferStartupAuditResult,
|
|
WorkerExecutionOfferStartupAuditor,
|
|
} from './workerExecutionOfferStartupAuditor';
|
|
import type {
|
|
WorkerExecutionOfferStartupRecoveryResult,
|
|
WorkerExecutionOfferStartupRecoverySupervisor,
|
|
} from './workerExecutionOfferStartupRecoverySupervisor';
|
|
|
|
export type WorkerExecutionOfferInboxStartResult =
|
|
| {
|
|
status: 'ready' | 'reconciliation_required';
|
|
audit: WorkerExecutionOfferStartupAuditResult;
|
|
recovery?: WorkerExecutionOfferStartupRecoveryResult;
|
|
}
|
|
| {
|
|
status: 'already_started';
|
|
audit: WorkerExecutionOfferStartupAuditResult;
|
|
recovery?: WorkerExecutionOfferStartupRecoveryResult;
|
|
};
|
|
|
|
export type WorkerExecutionOfferInboxStopResult =
|
|
| 'stopped'
|
|
| 'not_started'
|
|
| 'ownership_compromised';
|
|
|
|
export class WorkerExecutionOfferInboxStartupIncompleteError extends Error {
|
|
constructor(readonly audit: WorkerExecutionOfferStartupAuditResult) {
|
|
super('Worker execution offer inbox startup audit exhausted its budget');
|
|
this.name = 'WorkerExecutionOfferInboxStartupIncompleteError';
|
|
}
|
|
}
|
|
|
|
export class WorkerExecutionOfferInboxRecoveryIncompleteError extends Error {
|
|
constructor(readonly recovery: WorkerExecutionOfferStartupRecoveryResult) {
|
|
super('Worker execution offer inbox startup recovery exhausted its budget');
|
|
this.name = 'WorkerExecutionOfferInboxRecoveryIncompleteError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Owns the ordering contract: acquire the root, finish a bounded audit, then
|
|
* keep ownership until shutdown. It does not start a delivery transport.
|
|
*/
|
|
export class WorkerExecutionOfferInboxLifecycle {
|
|
private active = false;
|
|
private lastAudit?: WorkerExecutionOfferStartupAuditResult;
|
|
private lastRecovery?: WorkerExecutionOfferStartupRecoveryResult;
|
|
|
|
constructor(
|
|
private readonly ownership: WorkerExecutionOfferJournalOwnership,
|
|
private readonly auditor: Pick<WorkerExecutionOfferStartupAuditor, 'audit'>,
|
|
private readonly recovery?: Pick<
|
|
WorkerExecutionOfferStartupRecoverySupervisor,
|
|
'recover'
|
|
>,
|
|
) {}
|
|
|
|
async start(
|
|
currentSession: WorkerRecord,
|
|
): Promise<WorkerExecutionOfferInboxStartResult> {
|
|
if (this.active && this.lastAudit) {
|
|
return {
|
|
status: 'already_started',
|
|
audit: this.lastAudit,
|
|
...(this.lastRecovery === undefined
|
|
? {}
|
|
: { recovery: this.lastRecovery }),
|
|
};
|
|
}
|
|
await this.ownership.acquireOwnership();
|
|
try {
|
|
let audit = await this.auditor.audit(currentSession);
|
|
if (audit.status === 'scan_budget_exhausted') {
|
|
throw new WorkerExecutionOfferInboxStartupIncompleteError(audit);
|
|
}
|
|
let recovery: WorkerExecutionOfferStartupRecoveryResult | undefined;
|
|
if (this.recovery) {
|
|
recovery = await this.recovery.recover(audit);
|
|
if (recovery.status === 'action_budget_exhausted') {
|
|
throw new WorkerExecutionOfferInboxRecoveryIncompleteError(recovery);
|
|
}
|
|
audit = await this.auditor.audit(currentSession);
|
|
if (audit.status === 'scan_budget_exhausted') {
|
|
throw new WorkerExecutionOfferInboxStartupIncompleteError(audit);
|
|
}
|
|
}
|
|
this.active = true;
|
|
this.lastAudit = audit;
|
|
this.lastRecovery = recovery;
|
|
return {
|
|
status: audit.status,
|
|
audit,
|
|
...(recovery === undefined ? {} : { recovery }),
|
|
};
|
|
} catch (error) {
|
|
await this.ownership.releaseOwnership().catch(() => undefined);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
currentAudit(): WorkerExecutionOfferStartupAuditResult | undefined {
|
|
return this.lastAudit;
|
|
}
|
|
|
|
async stop(): Promise<WorkerExecutionOfferInboxStopResult> {
|
|
if (!this.active) return 'not_started';
|
|
this.active = false;
|
|
this.lastAudit = undefined;
|
|
this.lastRecovery = undefined;
|
|
const released = await this.ownership.releaseOwnership();
|
|
return released === 'released' || released === 'not_owned'
|
|
? 'stopped'
|
|
: 'ownership_compromised';
|
|
}
|
|
}
|