mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:02:22 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import type { ExecutionOrigin } from '../domain/run';
|
||||
import { selectOneLegacyExecution } from '../domain/legacyExecutionSelection';
|
||||
import type {
|
||||
LegacyExecutionCallbackFact,
|
||||
LegacyExecutionCancellationFact,
|
||||
LegacyExecutionSelector,
|
||||
} from '../ports/legacyExecutionCorrelation';
|
||||
import type {
|
||||
ActiveLegacyShadowRun,
|
||||
LegacyShadowRunLocator,
|
||||
} from '../ports/legacyShadowRunLocator';
|
||||
import type { LegacyShadowRunWriter } from './legacyShadowRunWriter';
|
||||
|
||||
export type LegacyCorrelationOperation =
|
||||
| 'cancel_all'
|
||||
| 'cancel_one'
|
||||
| 'callback';
|
||||
export type LegacyCorrelationFailureReason =
|
||||
| 'ambiguous'
|
||||
| 'truncated'
|
||||
| 'unmatched'
|
||||
| 'write_failed';
|
||||
|
||||
export interface LegacyCorrelationFailure {
|
||||
operation: LegacyCorrelationOperation;
|
||||
reason: LegacyCorrelationFailureReason;
|
||||
legacyCronId: number;
|
||||
candidateCount: number;
|
||||
}
|
||||
|
||||
export interface LegacyCorrelationReporter {
|
||||
failure(failure: LegacyCorrelationFailure): void;
|
||||
}
|
||||
|
||||
export interface LegacyCorrelationResult {
|
||||
matched: number;
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export class LegacyShadowRunCorrelator {
|
||||
constructor(
|
||||
private readonly locator: LegacyShadowRunLocator,
|
||||
private readonly writer: LegacyShadowRunWriter,
|
||||
private readonly reporter: LegacyCorrelationReporter,
|
||||
) {}
|
||||
|
||||
async cancel(
|
||||
fact: LegacyExecutionCancellationFact,
|
||||
origins: readonly ExecutionOrigin[],
|
||||
): Promise<LegacyCorrelationResult> {
|
||||
const lookup = await this.locator.listActiveByLegacyCron({
|
||||
legacyCronId: fact.legacyCronId,
|
||||
origins,
|
||||
});
|
||||
const candidates =
|
||||
fact.scope === 'all'
|
||||
? [...lookup.candidates]
|
||||
: this.selectOne(lookup.candidates, fact);
|
||||
if (lookup.truncated) {
|
||||
this.report({
|
||||
operation: fact.scope === 'all' ? 'cancel_all' : 'cancel_one',
|
||||
reason: 'truncated',
|
||||
legacyCronId: fact.legacyCronId,
|
||||
candidateCount: lookup.candidates.length,
|
||||
});
|
||||
}
|
||||
if (fact.scope === 'one' && candidates.length !== 1) {
|
||||
this.reportSelectionFailure('cancel_one', fact, lookup.candidates);
|
||||
return { matched: 0, truncated: lookup.truncated };
|
||||
}
|
||||
|
||||
let matched = 0;
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
await this.writer.cancelled(
|
||||
{ runId: candidate.runId, attemptId: candidate.attemptId },
|
||||
{ atMs: fact.atMs, reason: fact.reason },
|
||||
);
|
||||
matched += 1;
|
||||
} catch {
|
||||
this.report({
|
||||
operation: fact.scope === 'all' ? 'cancel_all' : 'cancel_one',
|
||||
reason: 'write_failed',
|
||||
legacyCronId: fact.legacyCronId,
|
||||
candidateCount: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
return { matched, truncated: lookup.truncated };
|
||||
}
|
||||
|
||||
async callback(
|
||||
fact: LegacyExecutionCallbackFact,
|
||||
origins: readonly ExecutionOrigin[],
|
||||
): Promise<LegacyCorrelationResult> {
|
||||
const lookup = await this.locator.listActiveByLegacyCron({
|
||||
legacyCronId: fact.legacyCronId,
|
||||
origins,
|
||||
});
|
||||
const candidates = this.selectOne(lookup.candidates, fact);
|
||||
if (lookup.truncated) {
|
||||
this.report({
|
||||
operation: 'callback',
|
||||
reason: 'truncated',
|
||||
legacyCronId: fact.legacyCronId,
|
||||
candidateCount: lookup.candidates.length,
|
||||
});
|
||||
}
|
||||
if (candidates.length !== 1) {
|
||||
this.reportSelectionFailure('callback', fact, lookup.candidates);
|
||||
return { matched: 0, truncated: lookup.truncated };
|
||||
}
|
||||
|
||||
const [candidate] = candidates;
|
||||
const reference = {
|
||||
runId: candidate.runId,
|
||||
attemptId: candidate.attemptId,
|
||||
};
|
||||
try {
|
||||
if (fact.phase === 'running') {
|
||||
await this.writer.spawned(reference, {
|
||||
atMs: fact.atMs,
|
||||
...(fact.pid === undefined ? {} : { pid: fact.pid }),
|
||||
...(fact.logArtifactId === undefined
|
||||
? {}
|
||||
: { logArtifactId: fact.logArtifactId }),
|
||||
});
|
||||
await this.writer.running(reference, fact.atMs);
|
||||
} else {
|
||||
await this.writer.spawned(reference, {
|
||||
atMs: fact.atMs,
|
||||
...(fact.pid === undefined ? {} : { pid: fact.pid }),
|
||||
...(fact.logArtifactId === undefined
|
||||
? {}
|
||||
: { logArtifactId: fact.logArtifactId }),
|
||||
});
|
||||
await this.writer.exited(reference, {
|
||||
atMs: fact.atMs,
|
||||
exitCode: fact.exitCode ?? 0,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
this.report({
|
||||
operation: 'callback',
|
||||
reason: 'write_failed',
|
||||
legacyCronId: fact.legacyCronId,
|
||||
candidateCount: 1,
|
||||
});
|
||||
return { matched: 0, truncated: lookup.truncated };
|
||||
}
|
||||
return { matched: 1, truncated: lookup.truncated };
|
||||
}
|
||||
|
||||
private selectOne(
|
||||
candidates: readonly ActiveLegacyShadowRun[],
|
||||
selector: LegacyExecutionSelector,
|
||||
): ActiveLegacyShadowRun[] {
|
||||
return selectOneLegacyExecution(candidates, selector);
|
||||
}
|
||||
|
||||
private reportSelectionFailure(
|
||||
operation: LegacyCorrelationOperation,
|
||||
selector: LegacyExecutionSelector,
|
||||
candidates: readonly ActiveLegacyShadowRun[],
|
||||
): void {
|
||||
this.report({
|
||||
operation,
|
||||
reason: candidates.length === 0 ? 'unmatched' : 'ambiguous',
|
||||
legacyCronId: selector.legacyCronId,
|
||||
candidateCount: candidates.length,
|
||||
});
|
||||
}
|
||||
|
||||
private report(failure: LegacyCorrelationFailure): void {
|
||||
try {
|
||||
this.reporter.failure(failure);
|
||||
} catch {
|
||||
// Correlation diagnostics must not affect legacy execution.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user