Files
qinglong/back/runtime/adapters/legacy/defaultManualPrimaryActivation.ts
T

272 lines
10 KiB
TypeScript

import type { Sequelize } from 'sequelize';
import { sequelize } from '../../../data';
import Logger from '../../../loaders/logger';
import {
PrimaryCompletionReceiptLifecycle,
type PrimaryCompletionReceiptLifecycleOptions,
} from '../../application/primaryCompletionReceiptLifecycle';
import { PrimaryCompletionReceiptJournalScanner } from '../../application/primaryCompletionReceiptJournalScanner';
import { PrimaryCompletionReceiptSupervisor } from '../../application/primaryCompletionReceiptSupervisor';
import { PrimaryCompletionReceiptConsumer } from '../../application/primaryCompletionReceiptConsumer';
import { PrimaryRunCompletionService } from '../../application/primaryRunCompletionService';
import {
PrimaryCancellationLifecycle,
type PrimaryCancellationLifecycleOptions,
} from '../../application/primaryCancellationLifecycle';
import { PrimaryCancellationDispatcher } from '../../application/primaryCancellationDispatcher';
import { PrimaryCancellationSupervisor } from '../../application/primaryCancellationSupervisor';
import { PrimaryTimeoutRequester } from '../../application/primaryTimeoutRequester';
import { PrimaryTimeoutSupervisor } from '../../application/primaryTimeoutSupervisor';
import {
PrimaryTimeoutLifecycle,
type PrimaryTimeoutLifecycleOptions,
} from '../../application/primaryTimeoutLifecycle';
import { RunCommandService } from '../../application/runCommandService';
import { PrimaryRunStartupReconciler } from '../../application/primaryRunStartupReconciler';
import {
PrimaryRunStartupSupervisor,
type PrimaryRunStartupOptions,
} from '../../application/primaryRunStartupSupervisor';
import type { RuntimeRolloutPolicy } from '../../domain/runtimeRollout';
import {
localPrimaryResourcePolicy,
type DeploymentProfile,
} from '../../domain/deploymentProfile';
import { LegacySequelizeCancellationDispatchRepository } from '../legacy-sequelize/cancellationDispatchRepository';
import { LegacySequelizePrimaryCancellationSource } from '../legacy-sequelize/primaryCancellationSource';
import { LegacySequelizePrimaryTimeoutSource } from '../legacy-sequelize/primaryTimeoutSource';
import { PrimaryCronProjection } from '../legacy-sequelize/primaryCronProjection';
import { LegacySequelizePrimaryRunRecoverySource } from '../legacy-sequelize/primaryRunRecoverySource';
import { LegacySequelizeCompletionReceiptJournal } from '../legacy-sequelize/completionReceiptJournal';
import { LegacySequelizeProjectedRunRepository } from '../legacy-sequelize/projectedRunRepository';
import { LocalProcessPersistedExecutionInspector } from '../local-process/localProcessIdentity';
import { LocalProcessPersistedExecutionController } from '../local-process/persistedLocalProcessController';
import { LocalProcessExecutor } from '../local-process/localProcessExecutor';
import { CompletionReceiptFileStore } from '../fs/completionReceiptFileStore';
import {
DEFAULT_COMPLETION_RECEIPT_ROOT,
DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH,
LegacyManualPrimaryLogFiles,
} from './defaultManualPrimaryRuntime';
import { ManualPrimaryRuntime } from '../../application/manualPrimaryRuntime';
export interface DefaultManualPrimaryActivationOptions {
database?: Sequelize;
owner?: string;
deploymentProfile?: DeploymentProfile;
recovery?: PrimaryRunStartupOptions;
completion?: Pick<
PrimaryCompletionReceiptLifecycleOptions,
'intervalMs' | 'initialDelayMs' | 'stopTimeoutMs' | 'cycle'
>;
cancellation?: Pick<
PrimaryCancellationLifecycleOptions,
'intervalMs' | 'initialDelayMs' | 'stopTimeoutMs' | 'cycle'
>;
timeout?: Pick<
PrimaryTimeoutLifecycleOptions,
'intervalMs' | 'initialDelayMs' | 'stopTimeoutMs' | 'cycle'
>;
}
function boundedOwner(value: string): string {
if (!value || value.length > 128) {
throw new RangeError(
'Primary activation owner must be 1 to 128 characters',
);
}
return value;
}
export function createDefaultManualPrimaryActivationStack(
rollout: RuntimeRolloutPolicy,
options: DefaultManualPrimaryActivationOptions = {},
) {
const database = options.database ?? sequelize;
const resources = localPrimaryResourcePolicy(
options.deploymentProfile ?? 'standalone',
);
const repository = new LegacySequelizeProjectedRunRepository(database, [
new PrimaryCronProjection(database),
]);
const recoverySource = new LegacySequelizePrimaryRunRecoverySource(database);
const completionReceiptJournal = new LegacySequelizeCompletionReceiptJournal(
database,
);
const completionReceiptStore = new CompletionReceiptFileStore(
DEFAULT_COMPLETION_RECEIPT_ROOT,
);
const completionReceipts = new PrimaryCompletionReceiptConsumer(
completionReceiptStore,
new PrimaryRunCompletionService(repository),
{
journal: completionReceiptJournal,
quarantineRetentionMs: resources.receiptQuarantineRetentionMs,
},
);
const startup = new PrimaryRunStartupSupervisor(
new PrimaryRunStartupReconciler(
repository,
recoverySource,
[new LocalProcessPersistedExecutionInspector()],
{
completionReceipts,
completionReceiptJournal,
receiptPublishGraceMs: resources.receiptPublishGraceMs,
},
),
);
const completion = new PrimaryCompletionReceiptLifecycle(
new PrimaryCompletionReceiptSupervisor(
new PrimaryCompletionReceiptJournalScanner(
completionReceiptJournal,
completionReceiptStore,
completionReceipts,
{
terminalMissingRetentionMs:
resources.receiptTerminalMissingRetentionMs,
},
),
),
{
intervalMs:
options.completion?.intervalMs ?? resources.completion.intervalMs,
initialDelayMs:
options.completion?.initialDelayMs ??
resources.completion.initialDelayMs,
stopTimeoutMs:
options.completion?.stopTimeoutMs ?? resources.completion.stopTimeoutMs,
cycle: options.completion?.cycle ?? {
pageSize: resources.completion.pageSize,
maxPages: resources.completion.maxPages,
},
onCycle(summary) {
Logger.info(
`[runtime-completion] ${JSON.stringify({
profile: resources.profile,
pages: summary.pages,
scanned: summary.scanned,
applied: summary.applied,
alreadyTerminal: summary.alreadyTerminal,
quarantined: summary.quarantined,
purgedQuarantines: summary.purgedQuarantines,
expiredMissing: summary.expiredMissing,
missing: summary.missing,
cleanupPending: summary.cleanupPending,
skipped: summary.skipped,
ambiguous: summary.ambiguous,
failed: summary.failed,
stopReason: summary.stopReason,
remaining: summary.remaining,
})}`,
);
},
onError() {
Logger.error('[runtime-completion] cycle failed');
},
},
);
const cancellation = new PrimaryCancellationLifecycle(
new PrimaryCancellationSupervisor(
new PrimaryCancellationDispatcher(
new LegacySequelizePrimaryCancellationSource(database),
new LegacySequelizeCancellationDispatchRepository(database),
[new LocalProcessPersistedExecutionController()],
{ owner: boundedOwner(options.owner ?? `http:${process.pid}`) },
),
),
{
intervalMs:
options.cancellation?.intervalMs ?? resources.cancellation.intervalMs,
initialDelayMs:
options.cancellation?.initialDelayMs ??
resources.cancellation.initialDelayMs,
stopTimeoutMs:
options.cancellation?.stopTimeoutMs ??
resources.cancellation.stopTimeoutMs,
cycle: options.cancellation?.cycle ?? {
pageSize: resources.cancellation.pageSize,
maxPages: resources.cancellation.maxPages,
},
onCycle(summary) {
Logger.info(
`[runtime-cancellation] ${JSON.stringify({
pages: summary.pages,
scanned: summary.scanned,
claimed: summary.claimed,
pending: summary.pending,
failed: summary.failed,
stopReason: summary.stopReason,
remaining: summary.remaining,
})}`,
);
},
onError() {
Logger.error('[runtime-cancellation] cycle failed');
},
},
);
const timeout = new PrimaryTimeoutLifecycle(
new PrimaryTimeoutSupervisor(
new PrimaryTimeoutRequester(
new LegacySequelizePrimaryTimeoutSource(database),
new RunCommandService(repository),
),
),
{
intervalMs: options.timeout?.intervalMs ?? resources.timeout.intervalMs,
initialDelayMs:
options.timeout?.initialDelayMs ?? resources.timeout.initialDelayMs,
stopTimeoutMs:
options.timeout?.stopTimeoutMs ?? resources.timeout.stopTimeoutMs,
cycle: options.timeout?.cycle ?? {
pageSize: resources.timeout.pageSize,
maxPages: resources.timeout.maxPages,
},
onCycle(summary) {
Logger.info(
`[runtime-timeout] ${JSON.stringify({
profile: resources.profile,
pages: summary.pages,
scanned: summary.scanned,
accepted: summary.accepted,
alreadyRequested: summary.alreadyRequested,
alreadyTerminal: summary.alreadyTerminal,
failed: summary.failed,
stopReason: summary.stopReason,
remaining: summary.remaining,
})}`,
);
},
onError() {
Logger.error('[runtime-timeout] cycle failed');
},
},
);
return {
router: new ManualPrimaryRuntime(
repository,
new LocalProcessExecutor({
durableLauncherPath: DEFAULT_LOCAL_PROCESS_LAUNCHER_PATH,
}),
rollout,
new LegacyManualPrimaryLogFiles(
undefined,
undefined,
completionReceiptJournal,
),
{
orchestrator: { completionReceiptJournal },
},
),
reconcile: () => startup.run(options.recovery),
startCompletion: () => completion.start(),
stopCompletion: () => completion.stop(),
startTimeout: () => timeout.start(),
stopTimeout: () => timeout.stop(),
startCancellation: () => cancellation.start(),
stopCancellation: () => cancellation.stop(),
};
}