start-command 0.35.3 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +10 -0
- package/package.json +1 -1
- package/src/bin/cli.js +43 -7
- package/src/lib/args-parser.js +12 -0
- package/src/lib/atomic-write.js +34 -0
- package/src/lib/attached-diagnostics.js +90 -0
- package/src/lib/cgroup-memory.js +43 -11
- package/src/lib/command-builder.js +40 -0
- package/src/lib/cpu-penalty-monitor.js +226 -0
- package/src/lib/cpu-penalty.js +175 -0
- package/src/lib/detached-finalize.js +47 -7
- package/src/lib/detached-output.js +61 -0
- package/src/lib/docker-cleanup.js +43 -4
- package/src/lib/docker-resource-limits.js +28 -0
- package/src/lib/docker-resource-options.js +180 -0
- package/src/lib/docker-runtime-args.js +15 -0
- package/src/lib/execution-attempt.js +153 -0
- package/src/lib/execution-recovery.js +191 -15
- package/src/lib/execution-resume-all.js +23 -1
- package/src/lib/execution-resume.js +203 -40
- package/src/lib/execution-store.js +56 -121
- package/src/lib/exit-evidence.js +123 -0
- package/src/lib/exit-reason.js +24 -6
- package/src/lib/isolation-log-utils.js +3 -3
- package/src/lib/isolation.js +36 -9
- package/src/lib/launch-owner.js +25 -0
- package/src/lib/launch-persistence.js +54 -0
- package/src/lib/query-commands.js +14 -3
- package/src/lib/resume-resources.js +88 -0
- package/src/lib/status-formatter.js +66 -6
- package/src/lib/store-lock.js +133 -0
- package/src/lib/usage.js +11 -0
- package/test/docker-runtime-options.js +6 -0
- package/test/execution-resume.js +10 -3
- package/test/exit-reason.js +6 -1
- package/test/isolation.js +5 -4
- package/test/regression-174.js +3 -1
- package/test/regression-176.js +5 -1
- package/test/regression-180.js +17 -10
- package/test/regression-182.js +7 -3
- package/test/regression-185.js +4 -9
- package/test/regression-187.js +547 -0
- package/test/regression-189.js +224 -0
- package/test/regression-190.js +111 -0
- package/test/regression-191.js +152 -0
- package/test/regression-192.js +66 -0
- package/test/regression-193.js +241 -0
- package/test/regression-194.js +182 -0
package/test/execution-resume.js
CHANGED
|
@@ -270,11 +270,14 @@ describe('resumeExecution', () => {
|
|
|
270
270
|
watchers.push([name, policy, logPath]),
|
|
271
271
|
});
|
|
272
272
|
assert.strictEqual(result.success, true, result.error);
|
|
273
|
-
assert.deepStrictEqual(
|
|
273
|
+
assert.deepStrictEqual(
|
|
274
|
+
calls.filter((call) => call[1] !== 'inspect'),
|
|
275
|
+
[['docker', 'start', 'box']]
|
|
276
|
+
);
|
|
274
277
|
assert.strictEqual(watchers.length, 1);
|
|
275
278
|
assert.strictEqual(watchers[0][0], 'box');
|
|
276
279
|
assert.strictEqual(watchers[0][2], '/tmp/session.log');
|
|
277
|
-
assert.strictEqual(store.saved.length,
|
|
280
|
+
assert.strictEqual(store.saved.length, 3);
|
|
278
281
|
assert.strictEqual(store.saved[0].uuid, record.uuid);
|
|
279
282
|
assert.strictEqual(store.saved[0].status, 'executing');
|
|
280
283
|
assert.strictEqual(store.saved[0].options.resumeCount, 1);
|
|
@@ -320,7 +323,10 @@ describe('resumeExecution', () => {
|
|
|
320
323
|
});
|
|
321
324
|
assert.strictEqual(result.success, false);
|
|
322
325
|
assert.match(result.error, /no such container/);
|
|
323
|
-
assert.strictEqual(store.saved.length,
|
|
326
|
+
assert.strictEqual(store.saved.length, 2);
|
|
327
|
+
assert.strictEqual(store.saved.at(-1).status, 'executing');
|
|
328
|
+
assert.strictEqual(store.saved.at(-1).options.sessionName, 'box');
|
|
329
|
+
assert.strictEqual(store.saved.at(-1).options.launchPending, undefined);
|
|
324
330
|
});
|
|
325
331
|
|
|
326
332
|
test('relaunches through the isolation backend when the container is gone', async () => {
|
|
@@ -329,6 +335,7 @@ describe('resumeExecution', () => {
|
|
|
329
335
|
const launches = [];
|
|
330
336
|
const result = await resumeExecution(store, 'box', {
|
|
331
337
|
probe: () => probeWith(SessionState.MISSING),
|
|
338
|
+
startWatcher: () => {},
|
|
332
339
|
runIsolated: (backend, command, options) => {
|
|
333
340
|
launches.push({ backend, command, options });
|
|
334
341
|
return Promise.resolve({ success: true, containerId: 'newid' });
|
package/test/exit-reason.js
CHANGED
|
@@ -111,7 +111,11 @@ describe('resolveExitReason', () => {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
it('should report the cgroup OOM observation when there is no marker', () => {
|
|
114
|
-
const reason = resolveExitReason({
|
|
114
|
+
const reason = resolveExitReason({
|
|
115
|
+
exitCode: 137,
|
|
116
|
+
oomKilled: true,
|
|
117
|
+
exitEvidence: { mainOom: true },
|
|
118
|
+
});
|
|
115
119
|
assert.strictEqual(reason, 'memory-exhaustion (cgroup-oom-killer)');
|
|
116
120
|
});
|
|
117
121
|
|
|
@@ -173,6 +177,7 @@ describe('resolveMemoryExhaustion', () => {
|
|
|
173
177
|
resolveMemoryExhaustion({
|
|
174
178
|
exitCode: 137,
|
|
175
179
|
logTail: 'killed\n',
|
|
180
|
+
exitEvidence: { mainOom: true },
|
|
176
181
|
oomKilled: true,
|
|
177
182
|
}),
|
|
178
183
|
{
|
package/test/isolation.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
supportsLogfileOption,
|
|
17
17
|
resetScreenVersionCache,
|
|
18
18
|
getDefaultDockerImage,
|
|
19
|
+
canRunLinuxDockerImages,
|
|
19
20
|
} = require('../src/lib/isolation');
|
|
20
21
|
|
|
21
22
|
describe('Isolation Module', () => {
|
|
@@ -665,8 +666,8 @@ describe('detectShellInEnvironment', () => {
|
|
|
665
666
|
});
|
|
666
667
|
|
|
667
668
|
it('should auto-detect shell in docker if docker is available', () => {
|
|
668
|
-
if (!
|
|
669
|
-
console.log(' Skipping:
|
|
669
|
+
if (!canRunLinuxDockerImages()) {
|
|
670
|
+
console.log(' Skipping: Docker cannot run Linux container images');
|
|
670
671
|
return;
|
|
671
672
|
}
|
|
672
673
|
// Use alpine:latest which is commonly available and has sh
|
|
@@ -703,8 +704,8 @@ describe('Shell option forwarding to isolation runners', () => {
|
|
|
703
704
|
|
|
704
705
|
describe('runInDocker shell option', () => {
|
|
705
706
|
it('should accept shell option in options object', async () => {
|
|
706
|
-
if (!
|
|
707
|
-
console.log(' Skipping:
|
|
707
|
+
if (!canRunLinuxDockerImages()) {
|
|
708
|
+
console.log(' Skipping: Docker cannot run Linux container images');
|
|
708
709
|
return;
|
|
709
710
|
}
|
|
710
711
|
|
package/test/regression-174.js
CHANGED
|
@@ -100,9 +100,11 @@ function runWatcher({ policy, running, state, withLog = true }) {
|
|
|
100
100
|
[
|
|
101
101
|
'#!/bin/sh',
|
|
102
102
|
`dir='${dir}'`,
|
|
103
|
+
'[ "$START_COMMAND_CGROUP_SAMPLING" = 1 ] && exit 1',
|
|
103
104
|
'case "$1" in',
|
|
104
105
|
' inspect)',
|
|
105
106
|
' case "$3" in',
|
|
107
|
+
' *HostConfig*) exit 1 ;;',
|
|
106
108
|
' *State.Running*)',
|
|
107
109
|
' answer=$(head -n 1 "$dir/running")',
|
|
108
110
|
' if [ "$(wc -l < "$dir/running")" -gt 1 ]; then',
|
|
@@ -173,7 +175,7 @@ describe('issue #174: docker logs -f returning is not the container exiting', ()
|
|
|
173
175
|
});
|
|
174
176
|
|
|
175
177
|
expect(run.status).toBe(0);
|
|
176
|
-
expect(run.calls).toContain('
|
|
178
|
+
expect(run.calls).toContain('inspect-running false');
|
|
177
179
|
expect(run.calls.indexOf('wait')).toBeGreaterThan(
|
|
178
180
|
run.calls.indexOf('logs')
|
|
179
181
|
);
|
package/test/regression-176.js
CHANGED
|
@@ -313,7 +313,11 @@ describe('issue #176: snapshot resume keeps the resource limits', () => {
|
|
|
313
313
|
expect(saved[0].options.sessionName).toBe('box-resume-1');
|
|
314
314
|
expect(result.output).toContain('--pids-limit=64');
|
|
315
315
|
// A snapshot container has no recovery selector.
|
|
316
|
-
expect(watchers[0][4]).toEqual({
|
|
316
|
+
expect(watchers[0][4]).toEqual({
|
|
317
|
+
recoverOnKill: false,
|
|
318
|
+
since: saved[0].attempt.startedAt,
|
|
319
|
+
attemptNumber: saved[0].attempt.number,
|
|
320
|
+
});
|
|
317
321
|
});
|
|
318
322
|
|
|
319
323
|
it('shows the limits in [Isolation] lines and stores them as metadata', () => {
|
package/test/regression-180.js
CHANGED
|
@@ -70,20 +70,20 @@ describe('issue #180: the sticky OOMKilled flag is not an exit reason', () => {
|
|
|
70
70
|
{
|
|
71
71
|
exitCode: 137,
|
|
72
72
|
oomKilled: true,
|
|
73
|
-
reason:
|
|
74
|
-
memory:
|
|
73
|
+
reason: 'signal (SIGKILL; cause unknown)',
|
|
74
|
+
memory: false,
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
exitCode: 137,
|
|
78
78
|
oomKilled: false,
|
|
79
|
-
reason: 'signal (SIGKILL)',
|
|
79
|
+
reason: 'signal (SIGKILL; cause unknown)',
|
|
80
80
|
memory: false,
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
exitCode: -1,
|
|
84
84
|
oomKilled: true,
|
|
85
|
-
reason:
|
|
86
|
-
memory:
|
|
85
|
+
reason: null,
|
|
86
|
+
memory: false,
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
exitCode: 139,
|
|
@@ -104,10 +104,17 @@ describe('issue #180: the sticky OOMKilled flag is not an exit reason', () => {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
it('
|
|
108
|
-
expect(isOomKillOfCommand({ exitCode: 137, oomKilled: true })).toBe(
|
|
109
|
-
expect(
|
|
110
|
-
|
|
107
|
+
it('requires fresh attribution in addition to SIGKILL or an unknown exit', () => {
|
|
108
|
+
expect(isOomKillOfCommand({ exitCode: 137, oomKilled: true })).toBe(false);
|
|
109
|
+
expect(
|
|
110
|
+
isOomKillOfCommand({
|
|
111
|
+
exitCode: 137,
|
|
112
|
+
oomKilled: true,
|
|
113
|
+
exitEvidence: { mainOom: true },
|
|
114
|
+
})
|
|
115
|
+
).toBe(true);
|
|
116
|
+
expect(isOomKillOfCommand({ exitCode: null, oomKilled: true })).toBe(false);
|
|
117
|
+
expect(isOomKillOfCommand({ exitCode: -1, oomKilled: true })).toBe(false);
|
|
111
118
|
expect(isOomKillOfCommand({ exitCode: 0, oomKilled: true })).toBe(false);
|
|
112
119
|
expect(isOomKillOfCommand({ exitCode: 1, oomKilled: true })).toBe(false);
|
|
113
120
|
expect(isOomKillOfCommand({ exitCode: 137, oomKilled: false })).toBe(false);
|
|
@@ -191,6 +198,6 @@ describe('issue #180: the sticky OOMKilled flag is not an exit reason', () => {
|
|
|
191
198
|
oomKilled: true,
|
|
192
199
|
logPath: null,
|
|
193
200
|
});
|
|
194
|
-
expect(main).toContain('Docker reports it was OOM-killed.');
|
|
201
|
+
expect(main).toContain('Docker reports a process in it was OOM-killed.');
|
|
195
202
|
});
|
|
196
203
|
});
|
package/test/regression-182.js
CHANGED
|
@@ -285,9 +285,13 @@ describe('issue #182: the watcher samples the cgroup while the container runs',
|
|
|
285
285
|
timeout: 20000,
|
|
286
286
|
});
|
|
287
287
|
expect(result.status).toBe(0);
|
|
288
|
-
expect(
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
expect(
|
|
289
|
+
fs
|
|
290
|
+
.readFileSync(path.join(host.dir, 'sample'), 'utf8')
|
|
291
|
+
.split(' ')
|
|
292
|
+
.slice(0, 4)
|
|
293
|
+
.join(' ')
|
|
294
|
+
).toBe(OOM_SAMPLE);
|
|
291
295
|
expect(fs.readFileSync(logPath, 'utf8')).toBe(
|
|
292
296
|
`${formatCgroupMemoryLogLine(OOM_SAMPLE)}\n`
|
|
293
297
|
);
|
package/test/regression-185.js
CHANGED
|
@@ -97,15 +97,10 @@ describe('issue #185: counter-only OOM scope is unknown', () => {
|
|
|
97
97
|
}
|
|
98
98
|
for (const exitCode of [137, null, -1]) {
|
|
99
99
|
const input = { exitCode, oomKilled: false, cgroupMemory };
|
|
100
|
-
expect(resolveExitReason(input)).toBe(CGROUP_OOM_EXIT_REASON);
|
|
101
|
-
expect(resolveMemoryExhaustion(input)).
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
: {
|
|
105
|
-
memoryExhausted: true,
|
|
106
|
-
memoryExhaustedReason: 'cgroup memory.events reported oom_kill=3',
|
|
107
|
-
}
|
|
108
|
-
);
|
|
100
|
+
expect(resolveExitReason(input)).not.toBe(CGROUP_OOM_EXIT_REASON);
|
|
101
|
+
expect(resolveMemoryExhaustion(input)).toBeNull();
|
|
102
|
+
const attributed = { ...input, exitEvidence: { mainOom: true } };
|
|
103
|
+
expect(resolveExitReason(attributed)).toBe(CGROUP_OOM_EXIT_REASON);
|
|
109
104
|
}
|
|
110
105
|
});
|
|
111
106
|
});
|