@halcyonlabs/app 0.47.0-halcyon.331 → 0.47.0-halcyon.333
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/dist/index.d.ts +30 -30
- package/package.json +1 -1
- package/server/dist/builtin-plugins/orbit/dist/assets/orbit/bundle/scripts/orbit-job-supervisor +108 -45
- package/server/dist/builtin-plugins/orbit/dist/assets/orbit/revision +1 -1
- package/server/dist/builtin-plugins/orbit/dist/server.js +320 -320
- package/server/dist/builtin-plugins/tasks/dist/app.js +31 -31
- package/server/dist/builtin-plugins/tasks/dist/server.js +92 -87
- package/server/dist/builtin-plugins/tasks/skills/tasks/SKILL.md +9 -0
- package/server/dist/start-server.js +35 -5
package/dist/index.d.ts
CHANGED
|
@@ -316,8 +316,8 @@ declare const appKeybindingOverridesSchema: z$1.ZodArray<z$1.ZodObject<{
|
|
|
316
316
|
shift: z$1.ZodBoolean;
|
|
317
317
|
}, z$1.core.$strict>>;
|
|
318
318
|
platform: z$1.ZodOptional<z$1.ZodEnum<{
|
|
319
|
-
windows: "windows";
|
|
320
319
|
mac: "mac";
|
|
320
|
+
windows: "windows";
|
|
321
321
|
linux: "linux";
|
|
322
322
|
}>>;
|
|
323
323
|
}, z$1.core.$strict>>;
|
|
@@ -524,8 +524,8 @@ declare const environmentSchema: z$1.ZodObject<{
|
|
|
524
524
|
retireAt: z$1.ZodNullable<z$1.ZodNumber>;
|
|
525
525
|
teardown: z$1.ZodNullable<z$1.ZodObject<{
|
|
526
526
|
status: z$1.ZodEnum<{
|
|
527
|
-
running: "running";
|
|
528
527
|
failed: "failed";
|
|
528
|
+
running: "running";
|
|
529
529
|
removed: "removed";
|
|
530
530
|
}>;
|
|
531
531
|
attempt: z$1.ZodNumber;
|
|
@@ -620,8 +620,8 @@ declare const hostSchema: z$1.ZodObject<{
|
|
|
620
620
|
pendingLog: z$1.ZodString;
|
|
621
621
|
teardown: z$1.ZodNullable<z$1.ZodObject<{
|
|
622
622
|
status: z$1.ZodEnum<{
|
|
623
|
-
running: "running";
|
|
624
623
|
failed: "failed";
|
|
624
|
+
running: "running";
|
|
625
625
|
removed: "removed";
|
|
626
626
|
}>;
|
|
627
627
|
attempt: z$1.ZodNumber;
|
|
@@ -732,8 +732,8 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
732
732
|
providerThreadId: z$1.ZodString;
|
|
733
733
|
objective: z$1.ZodString;
|
|
734
734
|
status: z$1.ZodEnum<{
|
|
735
|
-
paused: "paused";
|
|
736
735
|
active: "active";
|
|
736
|
+
paused: "paused";
|
|
737
737
|
budgetLimited: "budgetLimited";
|
|
738
738
|
complete: "complete";
|
|
739
739
|
}>;
|
|
@@ -1304,9 +1304,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
1304
1304
|
step: z$1.ZodString;
|
|
1305
1305
|
status: z$1.ZodOptional<z$1.ZodEnum<{
|
|
1306
1306
|
pending: "pending";
|
|
1307
|
+
active: "active";
|
|
1307
1308
|
completed: "completed";
|
|
1308
1309
|
failed: "failed";
|
|
1309
|
-
active: "active";
|
|
1310
1310
|
}>>;
|
|
1311
1311
|
}, z$1.core.$strip>>;
|
|
1312
1312
|
explanation: z$1.ZodOptional<z$1.ZodString>;
|
|
@@ -1385,10 +1385,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
1385
1385
|
}>;
|
|
1386
1386
|
taskStatus: z$1.ZodEnum<{
|
|
1387
1387
|
pending: "pending";
|
|
1388
|
-
running: "running";
|
|
1389
|
-
paused: "paused";
|
|
1390
1388
|
completed: "completed";
|
|
1391
1389
|
failed: "failed";
|
|
1390
|
+
paused: "paused";
|
|
1391
|
+
running: "running";
|
|
1392
1392
|
killed: "killed";
|
|
1393
1393
|
stopped: "stopped";
|
|
1394
1394
|
}>;
|
|
@@ -1405,8 +1405,8 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
1405
1405
|
index: z$1.ZodNumber;
|
|
1406
1406
|
label: z$1.ZodString;
|
|
1407
1407
|
state: z$1.ZodEnum<{
|
|
1408
|
-
running: "running";
|
|
1409
1408
|
failed: "failed";
|
|
1409
|
+
running: "running";
|
|
1410
1410
|
queued: "queued";
|
|
1411
1411
|
done: "done";
|
|
1412
1412
|
skipped: "skipped";
|
|
@@ -2076,9 +2076,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2076
2076
|
step: z$1.ZodString;
|
|
2077
2077
|
status: z$1.ZodOptional<z$1.ZodEnum<{
|
|
2078
2078
|
pending: "pending";
|
|
2079
|
+
active: "active";
|
|
2079
2080
|
completed: "completed";
|
|
2080
2081
|
failed: "failed";
|
|
2081
|
-
active: "active";
|
|
2082
2082
|
}>>;
|
|
2083
2083
|
}, z$1.core.$strip>>;
|
|
2084
2084
|
explanation: z$1.ZodOptional<z$1.ZodString>;
|
|
@@ -2157,10 +2157,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2157
2157
|
}>;
|
|
2158
2158
|
taskStatus: z$1.ZodEnum<{
|
|
2159
2159
|
pending: "pending";
|
|
2160
|
-
running: "running";
|
|
2161
|
-
paused: "paused";
|
|
2162
2160
|
completed: "completed";
|
|
2163
2161
|
failed: "failed";
|
|
2162
|
+
paused: "paused";
|
|
2163
|
+
running: "running";
|
|
2164
2164
|
killed: "killed";
|
|
2165
2165
|
stopped: "stopped";
|
|
2166
2166
|
}>;
|
|
@@ -2177,8 +2177,8 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2177
2177
|
index: z$1.ZodNumber;
|
|
2178
2178
|
label: z$1.ZodString;
|
|
2179
2179
|
state: z$1.ZodEnum<{
|
|
2180
|
-
running: "running";
|
|
2181
2180
|
failed: "failed";
|
|
2181
|
+
running: "running";
|
|
2182
2182
|
queued: "queued";
|
|
2183
2183
|
done: "done";
|
|
2184
2184
|
skipped: "skipped";
|
|
@@ -2389,10 +2389,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2389
2389
|
}>;
|
|
2390
2390
|
taskStatus: z$1.ZodEnum<{
|
|
2391
2391
|
pending: "pending";
|
|
2392
|
-
running: "running";
|
|
2393
|
-
paused: "paused";
|
|
2394
2392
|
completed: "completed";
|
|
2395
2393
|
failed: "failed";
|
|
2394
|
+
paused: "paused";
|
|
2395
|
+
running: "running";
|
|
2396
2396
|
killed: "killed";
|
|
2397
2397
|
stopped: "stopped";
|
|
2398
2398
|
}>;
|
|
@@ -2409,8 +2409,8 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2409
2409
|
index: z$1.ZodNumber;
|
|
2410
2410
|
label: z$1.ZodString;
|
|
2411
2411
|
state: z$1.ZodEnum<{
|
|
2412
|
-
running: "running";
|
|
2413
2412
|
failed: "failed";
|
|
2413
|
+
running: "running";
|
|
2414
2414
|
queued: "queued";
|
|
2415
2415
|
done: "done";
|
|
2416
2416
|
skipped: "skipped";
|
|
@@ -2488,10 +2488,10 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2488
2488
|
}>;
|
|
2489
2489
|
taskStatus: z$1.ZodEnum<{
|
|
2490
2490
|
pending: "pending";
|
|
2491
|
-
running: "running";
|
|
2492
|
-
paused: "paused";
|
|
2493
2491
|
completed: "completed";
|
|
2494
2492
|
failed: "failed";
|
|
2493
|
+
paused: "paused";
|
|
2494
|
+
running: "running";
|
|
2495
2495
|
killed: "killed";
|
|
2496
2496
|
stopped: "stopped";
|
|
2497
2497
|
}>;
|
|
@@ -2508,8 +2508,8 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2508
2508
|
index: z$1.ZodNumber;
|
|
2509
2509
|
label: z$1.ZodString;
|
|
2510
2510
|
state: z$1.ZodEnum<{
|
|
2511
|
-
running: "running";
|
|
2512
2511
|
failed: "failed";
|
|
2512
|
+
running: "running";
|
|
2513
2513
|
queued: "queued";
|
|
2514
2514
|
done: "done";
|
|
2515
2515
|
skipped: "skipped";
|
|
@@ -2704,9 +2704,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2704
2704
|
step: z$1.ZodString;
|
|
2705
2705
|
status: z$1.ZodOptional<z$1.ZodEnum<{
|
|
2706
2706
|
pending: "pending";
|
|
2707
|
+
active: "active";
|
|
2707
2708
|
completed: "completed";
|
|
2708
2709
|
failed: "failed";
|
|
2709
|
-
active: "active";
|
|
2710
2710
|
}>>;
|
|
2711
2711
|
}, z$1.core.$strip>>;
|
|
2712
2712
|
explanation: z$1.ZodOptional<z$1.ZodString>;
|
|
@@ -2715,8 +2715,8 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
2715
2715
|
threadId: z$1.ZodString;
|
|
2716
2716
|
providerThreadId: z$1.ZodString;
|
|
2717
2717
|
state: z$1.ZodEnum<{
|
|
2718
|
-
running: "running";
|
|
2719
2718
|
idle: "idle";
|
|
2719
|
+
running: "running";
|
|
2720
2720
|
}>;
|
|
2721
2721
|
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
2722
2722
|
type: z$1.ZodLiteral<"turn/diff/updated">;
|
|
@@ -3578,9 +3578,9 @@ declare const threadEventSchema: z$1.ZodUnion<readonly [z$1.ZodIntersection<z$1.
|
|
|
3578
3578
|
threadId: z$1.ZodString;
|
|
3579
3579
|
provisioningId: z$1.ZodString;
|
|
3580
3580
|
status: z$1.ZodEnum<{
|
|
3581
|
+
active: "active";
|
|
3581
3582
|
completed: "completed";
|
|
3582
3583
|
failed: "failed";
|
|
3583
|
-
active: "active";
|
|
3584
3584
|
cancelled: "cancelled";
|
|
3585
3585
|
}>;
|
|
3586
3586
|
environmentId: z$1.ZodNullable<z$1.ZodString>;
|
|
@@ -5766,9 +5766,9 @@ declare const hostDaemonCommandRegistry: {
|
|
|
5766
5766
|
}, z$1.core.$strict>, z$1.ZodDiscriminatedUnion<[z$1.ZodObject<{
|
|
5767
5767
|
kind: z$1.ZodLiteral<"wording">;
|
|
5768
5768
|
status: z$1.ZodEnum<{
|
|
5769
|
-
unavailable: "unavailable";
|
|
5770
5769
|
pass: "pass";
|
|
5771
5770
|
revise: "revise";
|
|
5771
|
+
unavailable: "unavailable";
|
|
5772
5772
|
}>;
|
|
5773
5773
|
}, z$1.core.$strict>, z$1.ZodObject<{
|
|
5774
5774
|
kind: z$1.ZodLiteral<"review">;
|
|
@@ -6129,8 +6129,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6129
6129
|
skillFilePath: z$1.ZodString;
|
|
6130
6130
|
}, z$1.core.$strict>], "kind">>;
|
|
6131
6131
|
instructionMode: z$1.ZodEnum<{
|
|
6132
|
-
append: "append";
|
|
6133
6132
|
replace: "replace";
|
|
6133
|
+
append: "append";
|
|
6134
6134
|
}>;
|
|
6135
6135
|
type: z$1.ZodLiteral<"thread.rewind.prepare">;
|
|
6136
6136
|
leaseId: z$1.ZodString;
|
|
@@ -6278,8 +6278,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6278
6278
|
skillFilePath: z$1.ZodString;
|
|
6279
6279
|
}, z$1.core.$strict>], "kind">>;
|
|
6280
6280
|
instructionMode: z$1.ZodEnum<{
|
|
6281
|
-
append: "append";
|
|
6282
6281
|
replace: "replace";
|
|
6282
|
+
append: "append";
|
|
6283
6283
|
}>;
|
|
6284
6284
|
type: z$1.ZodLiteral<"thread.start">;
|
|
6285
6285
|
requestId: z$1.ZodString;
|
|
@@ -6742,8 +6742,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6742
6742
|
envPassthrough: z$1.ZodArray<z$1.ZodString>;
|
|
6743
6743
|
}, z$1.core.$strict>;
|
|
6744
6744
|
instructionMode: z$1.ZodEnum<{
|
|
6745
|
-
append: "append";
|
|
6746
6745
|
replace: "replace";
|
|
6746
|
+
append: "append";
|
|
6747
6747
|
}>;
|
|
6748
6748
|
projectId: z$1.ZodString;
|
|
6749
6749
|
providerId: z$1.ZodString;
|
|
@@ -6958,8 +6958,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
6958
6958
|
envPassthrough: z$1.ZodArray<z$1.ZodString>;
|
|
6959
6959
|
}, z$1.core.$strict>;
|
|
6960
6960
|
instructionMode: z$1.ZodEnum<{
|
|
6961
|
-
append: "append";
|
|
6962
6961
|
replace: "replace";
|
|
6962
|
+
append: "append";
|
|
6963
6963
|
}>;
|
|
6964
6964
|
projectId: z$1.ZodString;
|
|
6965
6965
|
providerId: z$1.ZodString;
|
|
@@ -7860,13 +7860,13 @@ declare const hostDaemonCommandRegistry: {
|
|
|
7860
7860
|
modifiedAtMs: z$1.ZodOptional<z$1.ZodNumber>;
|
|
7861
7861
|
sha256: z$1.ZodString;
|
|
7862
7862
|
}, z$1.core.$strip>, z$1.ZodObject<{
|
|
7863
|
+
path: z$1.ZodString;
|
|
7863
7864
|
contentEncoding: z$1.ZodEnum<{
|
|
7864
7865
|
base64: "base64";
|
|
7865
7866
|
utf8: "utf8";
|
|
7866
7867
|
}>;
|
|
7867
7868
|
sha256: z$1.ZodString;
|
|
7868
7869
|
sizeBytes: z$1.ZodNumber;
|
|
7869
|
-
path: z$1.ZodString;
|
|
7870
7870
|
mimeType: z$1.ZodOptional<z$1.ZodString>;
|
|
7871
7871
|
modifiedAtMs: z$1.ZodOptional<z$1.ZodNumber>;
|
|
7872
7872
|
notModified: z$1.ZodLiteral<true>;
|
|
@@ -8131,8 +8131,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
8131
8131
|
supported: z$1.ZodLiteral<true>;
|
|
8132
8132
|
health: z$1.ZodObject<{
|
|
8133
8133
|
status: z$1.ZodEnum<{
|
|
8134
|
-
ready: "ready";
|
|
8135
8134
|
unknown: "unknown";
|
|
8135
|
+
ready: "ready";
|
|
8136
8136
|
not_installed: "not_installed";
|
|
8137
8137
|
unauthenticated: "unauthenticated";
|
|
8138
8138
|
expired: "expired";
|
|
@@ -8184,8 +8184,8 @@ declare const hostDaemonCommandRegistry: {
|
|
|
8184
8184
|
installed: z$1.ZodBoolean;
|
|
8185
8185
|
installSource: z$1.ZodEnum<{
|
|
8186
8186
|
notInstalled: "notInstalled";
|
|
8187
|
-
external: "external";
|
|
8188
8187
|
npmGlobal: "npmGlobal";
|
|
8188
|
+
external: "external";
|
|
8189
8189
|
}>;
|
|
8190
8190
|
currentVersion: z$1.ZodNullable<z$1.ZodString>;
|
|
8191
8191
|
latestVersion: z$1.ZodNullable<z$1.ZodString>;
|
|
@@ -8735,8 +8735,8 @@ declare const providerCliStatusResponseSchema: z$1.ZodRecord<z$1.ZodString, z$1.
|
|
|
8735
8735
|
installed: z$1.ZodBoolean;
|
|
8736
8736
|
installSource: z$1.ZodEnum<{
|
|
8737
8737
|
notInstalled: "notInstalled";
|
|
8738
|
-
external: "external";
|
|
8739
8738
|
npmGlobal: "npmGlobal";
|
|
8739
|
+
external: "external";
|
|
8740
8740
|
}>;
|
|
8741
8741
|
currentVersion: z$1.ZodNullable<z$1.ZodString>;
|
|
8742
8742
|
latestVersion: z$1.ZodNullable<z$1.ZodString>;
|
package/package.json
CHANGED
package/server/dist/builtin-plugins/orbit/dist/assets/orbit/bundle/scripts/orbit-job-supervisor
CHANGED
|
@@ -18,7 +18,9 @@ import subprocess
|
|
|
18
18
|
RESULT_SPACE_FLOOR = 1_000_000
|
|
19
19
|
MAIN_FREE_FLOOR = 15_000_000_000
|
|
20
20
|
START_HEADROOM = 5_000_000_000
|
|
21
|
-
|
|
21
|
+
EXPECTED_JOB_BYTES = 1_600_000_000
|
|
22
|
+
MIN_JOB_BYTES = 200_000_000
|
|
23
|
+
JOB_HISTORY_LENGTH = 16
|
|
22
24
|
# Many tiny files can exhaust inodes while bytes remain: the same rule applies
|
|
23
25
|
# to free file entries on filesystems that have a fixed number of them.
|
|
24
26
|
INODE_FLOOR = 500_000
|
|
@@ -389,6 +391,7 @@ class Workspaces:
|
|
|
389
391
|
f"{gigabytes(settings['disk_free_bytes'] + retained)} GB there. Free space on it first.")
|
|
390
392
|
# Persist ownership first. A crash before mkdir still leaves a reserved
|
|
391
393
|
# attempt which restart must explicitly finish, never silently forget.
|
|
394
|
+
write_record(self.state, manifest['id'] + '.expected', self.expected_job_bytes())
|
|
392
395
|
write_record(self.state, manifest['id'] + '.json', manifest, exclusive=True)
|
|
393
396
|
if not workspace:
|
|
394
397
|
return manifest
|
|
@@ -404,28 +407,57 @@ class Workspaces:
|
|
|
404
407
|
self.environment(manifest['id'])
|
|
405
408
|
return manifest
|
|
406
409
|
|
|
410
|
+
def job_history(self):
|
|
411
|
+
raw = ledger_setting(self.state, 'job-footprints', 4096)
|
|
412
|
+
history = [] if raw is None else json.loads(raw)
|
|
413
|
+
if (not isinstance(history, list) or len(history) > JOB_HISTORY_LENGTH
|
|
414
|
+
or any(type(n) is not int or not 0 <= n <= 2**63 - 1 for n in history)):
|
|
415
|
+
raise ValueError('Invalid finished job footprints.')
|
|
416
|
+
return history
|
|
417
|
+
|
|
418
|
+
def expected_job_bytes(self, job_id=None):
|
|
419
|
+
if job_id is not None:
|
|
420
|
+
raw = ledger_setting(self.state, job_id + '.expected', 32)
|
|
421
|
+
if raw is not None:
|
|
422
|
+
value = json.loads(raw)
|
|
423
|
+
if type(value) is not int or not MIN_JOB_BYTES <= value <= 2**63 - 1:
|
|
424
|
+
raise ValueError('Invalid job growth reservation.')
|
|
425
|
+
return value
|
|
426
|
+
history = self.job_history()
|
|
427
|
+
return min(EXPECTED_JOB_BYTES, max(MIN_JOB_BYTES, max(history) * 3 // 2)) if history else EXPECTED_JOB_BYTES
|
|
428
|
+
|
|
429
|
+
def remember_footprint(self, manifest):
|
|
430
|
+
if not has_workspace(manifest):
|
|
431
|
+
return
|
|
432
|
+
try:
|
|
433
|
+
usage = self.usage(manifest['id'])
|
|
434
|
+
if usage['complete']:
|
|
435
|
+
history = self.job_history()
|
|
436
|
+
write_record(self.state, 'job-footprints', (history + [usage['bytes']])[-JOB_HISTORY_LENGTH:])
|
|
437
|
+
except (OSError, ValueError):
|
|
438
|
+
return
|
|
439
|
+
|
|
407
440
|
def start_space(self, records, settings, keep=None):
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
expected = max(previous, settings['disk_start_headroom_bytes'])
|
|
413
|
-
sizes = {}
|
|
441
|
+
expected = self.expected_job_bytes(keep)
|
|
442
|
+
outstanding = 0
|
|
443
|
+
kept_size = 0
|
|
444
|
+
deadline = time.monotonic() + 5
|
|
414
445
|
for record in records:
|
|
415
446
|
if not holds_slot(record) and record['id'] != keep:
|
|
416
447
|
continue
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
448
|
+
size = 0
|
|
449
|
+
if has_workspace(record) and time.monotonic() < deadline:
|
|
450
|
+
try:
|
|
451
|
+
usage = self.usage(record['id'], deadline=deadline)
|
|
452
|
+
if usage['complete']:
|
|
453
|
+
size = usage['bytes']
|
|
454
|
+
except (OSError, ValueError):
|
|
455
|
+
pass
|
|
456
|
+
if record['id'] == keep:
|
|
457
|
+
kept_size = size
|
|
423
458
|
else:
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
write_record(self.state, 'expected-build-bytes', expected)
|
|
427
|
-
outstanding = sum(max(0, expected - size) for job, size in sizes.items() if job != keep)
|
|
428
|
-
new_growth = max(0, expected - sizes.get(keep, 0))
|
|
459
|
+
outstanding += max(0, self.expected_job_bytes(record['id']) - size)
|
|
460
|
+
new_growth = max(settings['disk_start_headroom_bytes'], expected - kept_size)
|
|
429
461
|
return settings['disk_free_bytes'] + outstanding + new_growth
|
|
430
462
|
|
|
431
463
|
def require_admission(self, records):
|
|
@@ -492,7 +524,7 @@ class Workspaces:
|
|
|
492
524
|
|
|
493
525
|
|
|
494
526
|
|
|
495
|
-
def usage(self, job_id):
|
|
527
|
+
def usage(self, job_id, *, deadline=None):
|
|
496
528
|
"""Size a workspace to choose which job to stop when the disk is low.
|
|
497
529
|
|
|
498
530
|
The walk is bounded to five seconds. A walk cut short by the time limit
|
|
@@ -505,23 +537,24 @@ class Workspaces:
|
|
|
505
537
|
self.read(job_id)
|
|
506
538
|
root = self.open_workspace(job_id)
|
|
507
539
|
device = os.fstat(root).st_dev
|
|
508
|
-
stack = [(root, os.scandir(root))]
|
|
509
|
-
deadline = time.monotonic() + 5
|
|
540
|
+
stack = [(root, os.scandir(root), '')]
|
|
541
|
+
deadline = min(deadline, time.monotonic() + 5) if deadline is not None else time.monotonic() + 5
|
|
510
542
|
try:
|
|
511
543
|
return self._walk(stack, device, deadline)
|
|
512
544
|
finally:
|
|
513
|
-
for fd, entries in reversed(stack):
|
|
545
|
+
for fd, entries, _ in reversed(stack):
|
|
514
546
|
entries.close()
|
|
515
547
|
os.close(fd)
|
|
516
548
|
|
|
517
549
|
def _walk(self, stack, device, deadline):
|
|
518
550
|
files = size = 0
|
|
551
|
+
complete = True
|
|
519
552
|
seen = set()
|
|
520
553
|
try:
|
|
521
554
|
while stack:
|
|
522
555
|
if time.monotonic() > deadline:
|
|
523
556
|
return dict(files=files, bytes=size, complete=False)
|
|
524
|
-
current, entries = stack[-1]
|
|
557
|
+
current, entries, parent = stack[-1]
|
|
525
558
|
entry = next(entries, None)
|
|
526
559
|
if entry is None:
|
|
527
560
|
entries.close()
|
|
@@ -540,6 +573,15 @@ class Workspaces:
|
|
|
540
573
|
size += metadata.st_blocks * 512
|
|
541
574
|
seen.add(identity)
|
|
542
575
|
if stat.S_ISDIR(metadata.st_mode):
|
|
576
|
+
if entry.name in MEASUREMENT_SKIPPED or (entry.name == 'build' and parent == 'results'):
|
|
577
|
+
child = os.open(entry.name, DIR_FLAGS, dir_fd=current)
|
|
578
|
+
try:
|
|
579
|
+
with os.scandir(child) as skipped:
|
|
580
|
+
if next(skipped, None) is not None:
|
|
581
|
+
complete = False
|
|
582
|
+
finally:
|
|
583
|
+
os.close(child)
|
|
584
|
+
continue
|
|
543
585
|
try:
|
|
544
586
|
child = os.open(entry.name, DIR_FLAGS, dir_fd=current)
|
|
545
587
|
except OSError as error:
|
|
@@ -549,8 +591,8 @@ class Workspaces:
|
|
|
549
591
|
if os.fstat(child).st_dev != device:
|
|
550
592
|
os.close(child)
|
|
551
593
|
raise ValueError('A workspace cannot contain another filesystem.')
|
|
552
|
-
stack.append((child, os.scandir(child)))
|
|
553
|
-
return dict(files=files, bytes=size, complete=
|
|
594
|
+
stack.append((child, os.scandir(child), entry.name))
|
|
595
|
+
return dict(files=files, bytes=size, complete=complete)
|
|
554
596
|
except OSError:
|
|
555
597
|
return dict(files=files, bytes=size, complete=False)
|
|
556
598
|
|
|
@@ -784,6 +826,8 @@ class Workspaces:
|
|
|
784
826
|
return self.save_transition(manifest, 'closed')
|
|
785
827
|
|
|
786
828
|
def save_transition(self, manifest, state, reason=None):
|
|
829
|
+
if state == 'stopping' and reason == 'finished':
|
|
830
|
+
self.remember_footprint(manifest)
|
|
787
831
|
updated = transition(manifest, state, reason)
|
|
788
832
|
write_record(self.state, manifest['id'] + '.json', updated)
|
|
789
833
|
return updated
|
|
@@ -1577,6 +1621,7 @@ def darwin_workspace_users(workspace):
|
|
|
1577
1621
|
|
|
1578
1622
|
UNPUSHED_SKIPPED = frozenset(('.git', 'node_modules', 'target', '.orbit-build', 'cargo-home', 'cargo-target',
|
|
1579
1623
|
'npm-cache', 'pnpm-store', 'pnpm-home', 'cache'))
|
|
1624
|
+
MEASUREMENT_SKIPPED = frozenset(('node_modules', 'target', 'cargo-target', 'pnpm-store', 'npm-cache'))
|
|
1580
1625
|
UNPUSHED_DEPTH = 6
|
|
1581
1626
|
UNPUSHED_SECONDS = 30
|
|
1582
1627
|
GIT_SECONDS = 20
|
|
@@ -1586,40 +1631,58 @@ GIT_SAFE = ('-c', 'core.fsmonitor=false', '-c', 'core.hooksPath=/dev/null', '-c'
|
|
|
1586
1631
|
|
|
1587
1632
|
|
|
1588
1633
|
def git_checkouts(root, deadline, include_build=False):
|
|
1589
|
-
def refuse(error):
|
|
1590
|
-
raise error
|
|
1591
|
-
|
|
1592
1634
|
found = []
|
|
1593
|
-
|
|
1635
|
+
pending = [Path(root)]
|
|
1636
|
+
while pending:
|
|
1594
1637
|
if time.monotonic() > deadline:
|
|
1595
1638
|
raise ValueError('Searching the work folder for Git checkouts took too long.')
|
|
1596
|
-
here =
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1639
|
+
here = pending.pop()
|
|
1640
|
+
folders = []
|
|
1641
|
+
markers = set()
|
|
1642
|
+
checkout = False
|
|
1643
|
+
with os.scandir(here) as entries:
|
|
1644
|
+
for entry in entries:
|
|
1645
|
+
if time.monotonic() > deadline:
|
|
1646
|
+
raise ValueError('Searching the work folder for Git checkouts took too long.')
|
|
1647
|
+
if entry.name in ('HEAD', 'objects', 'config'):
|
|
1648
|
+
markers.add(entry.name)
|
|
1649
|
+
if entry.name == '.git':
|
|
1650
|
+
if entry.is_dir(follow_symlinks=False):
|
|
1651
|
+
with os.scandir(entry.path) as metadata:
|
|
1652
|
+
checkout = next(metadata, None) is not None
|
|
1653
|
+
else:
|
|
1654
|
+
checkout = True
|
|
1655
|
+
if not entry.is_dir(follow_symlinks=False):
|
|
1656
|
+
continue
|
|
1657
|
+
if entry.name in UNPUSHED_SKIPPED and not (include_build and entry.name == '.orbit-build'):
|
|
1658
|
+
continue
|
|
1659
|
+
if here.name == 'results' and entry.name == 'build':
|
|
1660
|
+
continue
|
|
1661
|
+
if include_build or len(here.relative_to(root).parts) < UNPUSHED_DEPTH:
|
|
1662
|
+
folders.append(entry.name)
|
|
1663
|
+
if checkout or (include_build and {'HEAD', 'objects', 'config'}.issubset(markers)):
|
|
1602
1664
|
found.append(here)
|
|
1603
|
-
|
|
1604
|
-
folders[:] = []
|
|
1605
|
-
else:
|
|
1606
|
-
folders[:] = sorted(name for name in folders if name not in UNPUSHED_SKIPPED or (include_build and name == '.orbit-build'))
|
|
1665
|
+
pending.extend(here / name for name in sorted(folders, reverse=True))
|
|
1607
1666
|
return found
|
|
1608
1667
|
|
|
1609
1668
|
|
|
1610
|
-
def git_output(account, checkout, name, *args, allowed=(0,), stdin='', offline=False):
|
|
1669
|
+
def git_output(account, checkout, name, *args, allowed=(0,), stdin='', offline=False, deadline=None):
|
|
1611
1670
|
import shutil
|
|
1612
1671
|
import subprocess
|
|
1613
1672
|
program = shutil.which('git', path=GIT_PATH)
|
|
1614
1673
|
if program is None:
|
|
1615
1674
|
raise ValueError('Git is not installed, so unpushed work cannot be checked.')
|
|
1616
1675
|
environment = dict(PATH=GIT_PATH, HOME=account.pw_dir, LC_ALL='C', GIT_CONFIG_NOSYSTEM='1',
|
|
1617
|
-
GIT_CONFIG_GLOBAL='/dev/null', GIT_OPTIONAL_LOCKS='0', GIT_TERMINAL_PROMPT='0'
|
|
1676
|
+
GIT_CONFIG_GLOBAL='/dev/null', GIT_OPTIONAL_LOCKS='0', GIT_TERMINAL_PROMPT='0',
|
|
1677
|
+
GIT_CEILING_DIRECTORIES=str(Path(checkout).parent))
|
|
1618
1678
|
if offline:
|
|
1619
1679
|
environment.update(GIT_NO_LAZY_FETCH='1', GIT_ALLOW_PROTOCOL='', GIT_PAGER='cat')
|
|
1680
|
+
timeout = GIT_SECONDS if deadline is None else min(GIT_SECONDS, deadline - time.monotonic())
|
|
1681
|
+
if timeout <= 0:
|
|
1682
|
+
raise ValueError('Checking the work folder for unpushed commits took too long.')
|
|
1620
1683
|
try:
|
|
1621
1684
|
result = subprocess.run([program, '-C', str(checkout), *GIT_SAFE, *args], cwd='/', env=environment, input=stdin,
|
|
1622
|
-
capture_output=True, text=True, errors='replace', timeout=
|
|
1685
|
+
capture_output=True, text=True, errors='replace', timeout=timeout)
|
|
1623
1686
|
except subprocess.TimeoutExpired:
|
|
1624
1687
|
raise ValueError(f'Git took too long to read {name}.')
|
|
1625
1688
|
if result.returncode not in allowed:
|
|
@@ -1628,9 +1691,9 @@ def git_output(account, checkout, name, *args, allowed=(0,), stdin='', offline=F
|
|
|
1628
1691
|
return result
|
|
1629
1692
|
|
|
1630
1693
|
|
|
1631
|
-
def unpushed_checkout(account, checkout, name):
|
|
1694
|
+
def unpushed_checkout(account, checkout, name, deadline=None):
|
|
1632
1695
|
def git(*args, allowed=(0,)):
|
|
1633
|
-
return git_output(account, checkout, name, *args, allowed=allowed)
|
|
1696
|
+
return git_output(account, checkout, name, *args, allowed=allowed, deadline=deadline)
|
|
1634
1697
|
|
|
1635
1698
|
def count(*revisions):
|
|
1636
1699
|
return int(git('rev-list', '--count', *revisions).stdout.strip() or '0')
|
|
@@ -1666,7 +1729,7 @@ def search_unpushed(root, account, seconds, include_build=False):
|
|
|
1666
1729
|
if time.monotonic() > deadline:
|
|
1667
1730
|
raise ValueError('Checking the work folder for unpushed commits took too long.')
|
|
1668
1731
|
name = str(checkout.relative_to(root))
|
|
1669
|
-
work = unpushed_checkout(account, checkout, name)
|
|
1732
|
+
work = unpushed_checkout(account, checkout, name, deadline)
|
|
1670
1733
|
if work['branches'] or work['detached'] or work['uncommitted'] or work['untracked'] or work['stashes']:
|
|
1671
1734
|
found.append(dict(path=name, **work))
|
|
1672
1735
|
return dict(checkouts=found)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
55a68a0b352242b1c815daadd214812f7764a927
|