mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:25:29 +08:00
146 lines
4.0 KiB
JavaScript
146 lines
4.0 KiB
JavaScript
require('ts-node/register/transpile-only');
|
|
|
|
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
const {
|
|
createLocalProcessDurableHandle,
|
|
} = require('../../back/runtime/adapters/local-process/localProcessIdentity');
|
|
const {
|
|
LocalProcessPersistedExecutionController,
|
|
} = require('../../back/runtime/adapters/local-process/persistedLocalProcessController');
|
|
|
|
const IDENTITY = {
|
|
platform: 'linux',
|
|
bootId: '11111111-2222-3333-4444-555555555555',
|
|
pid: 4321,
|
|
processGroupId: 4321,
|
|
startTimeTicks: '123456',
|
|
};
|
|
const HANDLE = createLocalProcessDurableHandle('handle-1', IDENTITY);
|
|
|
|
function controller(inspections, overrides = {}) {
|
|
const signals = [];
|
|
let index = 0;
|
|
return {
|
|
signals,
|
|
value: new LocalProcessPersistedExecutionController({
|
|
identityProvider: {
|
|
async capture() {
|
|
throw new Error('not used');
|
|
},
|
|
async inspect(identity) {
|
|
assert.deepEqual(identity, IDENTITY);
|
|
const status = inspections[Math.min(index, inspections.length - 1)];
|
|
index += 1;
|
|
return { status };
|
|
},
|
|
},
|
|
sendSignal(pid, signal) {
|
|
signals.push({ pid, signal });
|
|
},
|
|
graceMs: 10,
|
|
pollIntervalMs: 5,
|
|
sleep: async () => undefined,
|
|
...overrides,
|
|
}),
|
|
};
|
|
}
|
|
|
|
const REASON = { kind: 'user', requestedAtMs: 1_750_000_000_000 };
|
|
|
|
test('refuses malformed, PID-mismatched, and non-leader handles without signals', async () => {
|
|
const instance = controller(['running']);
|
|
assert.equal(
|
|
(await instance.value.stop({ durableHandle: 'bad', reason: REASON }))
|
|
.status,
|
|
'invalid',
|
|
);
|
|
assert.equal(
|
|
(
|
|
await instance.value.stop({
|
|
durableHandle: HANDLE,
|
|
expectedPid: 9999,
|
|
reason: REASON,
|
|
})
|
|
).status,
|
|
'pid_mismatch',
|
|
);
|
|
const nonLeader = createLocalProcessDurableHandle('handle-2', {
|
|
...IDENTITY,
|
|
processGroupId: 4000,
|
|
});
|
|
assert.equal(
|
|
(await instance.value.stop({ durableHandle: nonLeader, reason: REASON }))
|
|
.status,
|
|
'identity_mismatch',
|
|
);
|
|
assert.deepEqual(instance.signals, []);
|
|
});
|
|
|
|
test('sends TERM only when the persisted process exits during grace', async () => {
|
|
const instance = controller(['running', 'exited']);
|
|
const result = await instance.value.stop({
|
|
durableHandle: HANDLE,
|
|
expectedPid: 4321,
|
|
reason: REASON,
|
|
});
|
|
assert.deepEqual(result, {
|
|
status: 'termination_requested',
|
|
termSignalSent: true,
|
|
killSignalSent: false,
|
|
});
|
|
assert.deepEqual(instance.signals, [{ pid: -4321, signal: 'SIGTERM' }]);
|
|
});
|
|
|
|
test('revalidates identity before escalating a persistent process to KILL', async () => {
|
|
const persistent = controller(['running', 'running', 'running', 'running']);
|
|
const killed = await persistent.value.stop({
|
|
durableHandle: HANDLE,
|
|
reason: REASON,
|
|
});
|
|
assert.equal(killed.killSignalSent, true);
|
|
assert.deepEqual(persistent.signals, [
|
|
{ pid: -4321, signal: 'SIGTERM' },
|
|
{ pid: -4321, signal: 'SIGKILL' },
|
|
]);
|
|
|
|
const changed = controller([
|
|
'running',
|
|
'running',
|
|
'running',
|
|
'identity_mismatch',
|
|
]);
|
|
const refused = await changed.value.stop({
|
|
durableHandle: HANDLE,
|
|
reason: REASON,
|
|
});
|
|
assert.equal(refused.status, 'identity_mismatch');
|
|
assert.equal(refused.killSignalSent, false);
|
|
assert.deepEqual(changed.signals, [{ pid: -4321, signal: 'SIGTERM' }]);
|
|
});
|
|
|
|
test('treats an already exited process and ESRCH as idempotent completion', async () => {
|
|
const exited = controller(['exited']);
|
|
assert.deepEqual(
|
|
await exited.value.stop({ durableHandle: HANDLE, reason: REASON }),
|
|
{
|
|
status: 'already_exited',
|
|
termSignalSent: false,
|
|
killSignalSent: false,
|
|
},
|
|
);
|
|
|
|
const noProcess = controller(['running'], {
|
|
sendSignal() {
|
|
const error = new Error('gone');
|
|
error.code = 'ESRCH';
|
|
throw error;
|
|
},
|
|
});
|
|
assert.equal(
|
|
(await noProcess.value.stop({ durableHandle: HANDLE, reason: REASON }))
|
|
.status,
|
|
'already_exited',
|
|
);
|
|
});
|