mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:00:24 +08:00
331 lines
8.6 KiB
JavaScript
331 lines
8.6 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
createRunAttemptLogRetirementRecord,
|
|
RunAttemptLogRetentionUnavailableError,
|
|
} = require('@qinglong/runtime-core/run-attempt-log-retention');
|
|
const {
|
|
PostgresRunAttemptLogRetentionClaimRepository,
|
|
} = require('../dist/entrypoints/runtime');
|
|
|
|
const TOKEN = '00000000-0000-4000-8000-000000000055';
|
|
const ARTIFACT_ID = `wlog-${'a'.repeat(30)}`;
|
|
|
|
function claimRow(overrides = {}) {
|
|
return {
|
|
projectId: 'project-1',
|
|
runId: 'run-1',
|
|
attemptId: 'attempt-1',
|
|
logArtifactId: ARTIFACT_ID,
|
|
executorType: 'remote_worker',
|
|
finishedAtMs: '1000',
|
|
eligibleAtMs: '61000',
|
|
observedAtMs: '70000',
|
|
claimOwner: 'replica-a',
|
|
claimToken: TOKEN,
|
|
claimVersion: 1,
|
|
claimExpiresAtMs: '100000',
|
|
failureCount: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function claim() {
|
|
return Object.freeze({
|
|
candidate: Object.freeze({
|
|
projectId: 'project-1',
|
|
runId: 'run-1',
|
|
attemptId: 'attempt-1',
|
|
logArtifactId: ARTIFACT_ID,
|
|
executorType: 'remote_worker',
|
|
finishedAtMs: 1000,
|
|
}),
|
|
eligibleAtMs: 61000,
|
|
observedAtMs: 70000,
|
|
ownerId: 'replica-a',
|
|
token: TOKEN,
|
|
version: 1,
|
|
expiresAtMs: 100000,
|
|
failureCount: 0,
|
|
});
|
|
}
|
|
|
|
test('claims one bounded remote log page under a short database lease', async () => {
|
|
const calls = [];
|
|
let released = false;
|
|
const repository = new PostgresRunAttemptLogRetentionClaimRepository(
|
|
{
|
|
async connect() {
|
|
return {
|
|
async query(text, values = []) {
|
|
calls.push({ text, values });
|
|
if (text.includes('FOR UPDATE OF attempt SKIP LOCKED')) {
|
|
return { rows: [claimRow()], rowCount: 1 };
|
|
}
|
|
return { rows: [], rowCount: 0 };
|
|
},
|
|
release() {
|
|
released = true;
|
|
},
|
|
};
|
|
},
|
|
async query() {
|
|
throw new Error('pool query not expected');
|
|
},
|
|
},
|
|
() => TOKEN,
|
|
);
|
|
|
|
const page = await repository.claim({
|
|
ownerId: 'replica-a',
|
|
retentionMs: 60000,
|
|
limit: 4,
|
|
leaseMs: 30000,
|
|
});
|
|
|
|
assert.deepEqual(page, { claims: [claim()], hasMore: false });
|
|
assert.deepEqual(
|
|
calls.map(({ text }) => text.split('\n', 1)[0]),
|
|
[
|
|
'BEGIN ISOLATION LEVEL READ COMMITTED',
|
|
"SET LOCAL statement_timeout = '5000ms'",
|
|
"SET LOCAL lock_timeout = '1000ms'",
|
|
'WITH observation AS (',
|
|
'COMMIT',
|
|
],
|
|
);
|
|
assert.deepEqual(calls[3].values, [
|
|
60000,
|
|
4,
|
|
'replica-a',
|
|
TOKEN,
|
|
30000,
|
|
]);
|
|
assert.match(calls[3].text, /ON CONFLICT \(attempt_id\) DO UPDATE/);
|
|
assert.match(calls[3].text, /claim_expires_at_ms <= EXCLUDED\.updated_at_ms/);
|
|
assert.equal(released, true);
|
|
});
|
|
|
|
test('fences retry settlement by owner token version and database expiry', async () => {
|
|
const calls = [];
|
|
const repository = new PostgresRunAttemptLogRetentionClaimRepository({
|
|
async connect() {
|
|
throw new Error('not expected');
|
|
},
|
|
async query(text, values) {
|
|
calls.push({ text, values });
|
|
return { rows: [{ attemptId: 'attempt-1' }], rowCount: 1 };
|
|
},
|
|
});
|
|
|
|
assert.equal(
|
|
await repository.settle(claim(), {
|
|
status: 'retry',
|
|
delayMs: 2500,
|
|
failureCode: 'artifact_unavailable',
|
|
}),
|
|
'settled',
|
|
);
|
|
assert.deepEqual(calls[0].values, [
|
|
'attempt-1',
|
|
'replica-a',
|
|
TOKEN,
|
|
1,
|
|
100000,
|
|
'retry',
|
|
2500,
|
|
'artifact_unavailable',
|
|
]);
|
|
assert.match(calls[0].text, /claim_expires_at_ms > observation\.observed_at_ms/);
|
|
});
|
|
|
|
test('reads an exact durable tombstone for the profile-aware log route', async () => {
|
|
const record = createRunAttemptLogRetirementRecord({
|
|
...claim().candidate,
|
|
eligibleAtMs: 61000,
|
|
retiredAtMs: 80000,
|
|
disposition: 'already_absent',
|
|
byteLength: 0,
|
|
truncation: { truncated: 'unknown' },
|
|
});
|
|
const rows = [
|
|
{
|
|
...record,
|
|
finishedAtMs: String(record.finishedAtMs),
|
|
eligibleAtMs: String(record.eligibleAtMs),
|
|
retiredAtMs: String(record.retiredAtMs),
|
|
byteLength: String(record.byteLength),
|
|
truncated: 'unknown',
|
|
maximumBytes: null,
|
|
truncationObservedAtMs: null,
|
|
},
|
|
];
|
|
const repository = new PostgresRunAttemptLogRetentionClaimRepository({
|
|
async connect() {
|
|
throw new Error('not expected');
|
|
},
|
|
async query(text, values) {
|
|
assert.match(text, /artifact_tombstones/);
|
|
assert.deepEqual(values, [ARTIFACT_ID]);
|
|
return { rows: rows.splice(0), rowCount: 1 };
|
|
},
|
|
});
|
|
const identity = {
|
|
projectId: 'project-1',
|
|
runId: 'run-1',
|
|
attemptId: 'attempt-1',
|
|
logArtifactId: ARTIFACT_ID,
|
|
};
|
|
|
|
assert.deepEqual(await repository.inspect(identity), {
|
|
status: 'retired',
|
|
record,
|
|
});
|
|
assert.deepEqual(await repository.inspect(identity), { status: 'active' });
|
|
});
|
|
|
|
test('records the exact tombstone and removes its claim atomically', async () => {
|
|
const record = createRunAttemptLogRetirementRecord({
|
|
...claim().candidate,
|
|
eligibleAtMs: 61000,
|
|
retiredAtMs: 80000,
|
|
disposition: 'deleted',
|
|
byteLength: 11,
|
|
truncation: {
|
|
truncated: false,
|
|
maximumBytes: 1048576,
|
|
observedAtMs: 80000,
|
|
},
|
|
});
|
|
const calls = [];
|
|
let released = false;
|
|
const repository = new PostgresRunAttemptLogRetentionClaimRepository({
|
|
async connect() {
|
|
return {
|
|
async query(text, values = []) {
|
|
calls.push({ text, values });
|
|
if (text.includes('FOR UPDATE OF control')) {
|
|
return {
|
|
rows: [claimRow({ observedAtMs: '80000' })],
|
|
rowCount: 1,
|
|
};
|
|
}
|
|
if (text.startsWith('INSERT INTO "ql3"."run_attempt_log_artifact_tombstones"')) {
|
|
return {
|
|
rows: [{ recordDigest: record.recordDigest }],
|
|
rowCount: 1,
|
|
};
|
|
}
|
|
if (text.startsWith('DELETE FROM "ql3"."run_attempt_log_retention_controls"')) {
|
|
return { rows: [], rowCount: 1 };
|
|
}
|
|
return { rows: [], rowCount: 0 };
|
|
},
|
|
release() {
|
|
released = true;
|
|
},
|
|
};
|
|
},
|
|
async query() {
|
|
throw new Error('not expected');
|
|
},
|
|
});
|
|
|
|
assert.equal(
|
|
await repository.settle(claim(), { status: 'retired', record }),
|
|
'settled',
|
|
);
|
|
assert.deepEqual(
|
|
calls.map(({ text }) => text.split('\n', 1)[0]),
|
|
[
|
|
'BEGIN ISOLATION LEVEL READ COMMITTED',
|
|
"SET LOCAL statement_timeout = '5000ms'",
|
|
"SET LOCAL lock_timeout = '1000ms'",
|
|
'WITH observation AS (',
|
|
'INSERT INTO "ql3"."run_attempt_log_artifact_tombstones" (',
|
|
'DELETE FROM "ql3"."run_attempt_log_retention_controls"',
|
|
'COMMIT',
|
|
],
|
|
);
|
|
assert.equal(calls[4].values.at(-1), record.recordDigest);
|
|
assert.equal(released, true);
|
|
});
|
|
|
|
test('returns fenced without writing when the durable lease changed', async () => {
|
|
const calls = [];
|
|
const repository = new PostgresRunAttemptLogRetentionClaimRepository({
|
|
async connect() {
|
|
return {
|
|
async query(text) {
|
|
calls.push(text);
|
|
if (text.includes('FOR UPDATE OF control')) {
|
|
return { rows: [], rowCount: 0 };
|
|
}
|
|
return { rows: [], rowCount: 0 };
|
|
},
|
|
release() {},
|
|
};
|
|
},
|
|
async query() {
|
|
throw new Error('not expected');
|
|
},
|
|
});
|
|
const record = createRunAttemptLogRetirementRecord({
|
|
...claim().candidate,
|
|
eligibleAtMs: 61000,
|
|
retiredAtMs: 80000,
|
|
disposition: 'already_absent',
|
|
byteLength: 0,
|
|
truncation: { truncated: 'unknown' },
|
|
});
|
|
|
|
assert.equal(
|
|
await repository.settle(claim(), { status: 'retired', record }),
|
|
'fenced',
|
|
);
|
|
assert.equal(
|
|
calls.some((text) => text.includes('artifact_tombstones" (')),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('rolls back and wraps claim failures without leaking the client', async () => {
|
|
const calls = [];
|
|
let released = false;
|
|
const repository = new PostgresRunAttemptLogRetentionClaimRepository(
|
|
{
|
|
async connect() {
|
|
return {
|
|
async query(text) {
|
|
calls.push(text);
|
|
if (text.includes('FOR UPDATE OF attempt')) throw new Error('offline');
|
|
return { rows: [], rowCount: 0 };
|
|
},
|
|
release() {
|
|
released = true;
|
|
},
|
|
};
|
|
},
|
|
async query() {
|
|
throw new Error('not expected');
|
|
},
|
|
},
|
|
() => TOKEN,
|
|
);
|
|
|
|
await assert.rejects(
|
|
repository.claim({
|
|
ownerId: 'replica-a',
|
|
retentionMs: 60000,
|
|
limit: 1,
|
|
leaseMs: 5000,
|
|
}),
|
|
RunAttemptLogRetentionUnavailableError,
|
|
);
|
|
assert.equal(calls.at(-1), 'ROLLBACK');
|
|
assert.equal(released, true);
|
|
});
|