@ash-ai/sandbox 0.0.1
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/LICENSE +21 -0
- package/dist/bridge-client.d.ts +26 -0
- package/dist/bridge-client.d.ts.map +1 -0
- package/dist/bridge-client.js +123 -0
- package/dist/bridge-client.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +39 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +161 -0
- package/dist/manager.js.map +1 -0
- package/dist/pool.d.ts +69 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +251 -0
- package/dist/pool.js.map +1 -0
- package/dist/resource-limits.d.ts +21 -0
- package/dist/resource-limits.d.ts.map +1 -0
- package/dist/resource-limits.js +139 -0
- package/dist/resource-limits.js.map +1 -0
- package/dist/snapshot-gcs.d.ts +13 -0
- package/dist/snapshot-gcs.d.ts.map +1 -0
- package/dist/snapshot-gcs.js +63 -0
- package/dist/snapshot-gcs.js.map +1 -0
- package/dist/snapshot-s3.d.ts +13 -0
- package/dist/snapshot-s3.d.ts.map +1 -0
- package/dist/snapshot-s3.js +75 -0
- package/dist/snapshot-s3.js.map +1 -0
- package/dist/snapshot-store.d.ts +29 -0
- package/dist/snapshot-store.d.ts.map +1 -0
- package/dist/snapshot-store.js +74 -0
- package/dist/snapshot-store.js.map +1 -0
- package/dist/state-persistence.d.ts +45 -0
- package/dist/state-persistence.d.ts.map +1 -0
- package/dist/state-persistence.js +208 -0
- package/dist/state-persistence.js.map +1 -0
- package/package.json +46 -0
package/dist/pool.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { DEFAULT_MAX_SANDBOXES, DEFAULT_IDLE_TIMEOUT_MS, IDLE_SWEEP_INTERVAL_MS } from '@ash-ai/shared';
|
|
2
|
+
import { deleteSessionState, deleteCloudState } from './state-persistence.js';
|
|
3
|
+
export class SandboxPool {
|
|
4
|
+
live = new Map();
|
|
5
|
+
sessionIndex = new Map(); // sessionId → sandboxId
|
|
6
|
+
manager;
|
|
7
|
+
db;
|
|
8
|
+
dataDir;
|
|
9
|
+
maxCapacity;
|
|
10
|
+
idleTimeoutMs;
|
|
11
|
+
onBeforeEvict;
|
|
12
|
+
sweepTimer = null;
|
|
13
|
+
resumeWarmHits = 0;
|
|
14
|
+
resumeColdHits = 0;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.manager = opts.manager;
|
|
17
|
+
this.db = opts.db;
|
|
18
|
+
this.dataDir = opts.dataDir;
|
|
19
|
+
this.maxCapacity = opts.maxCapacity ?? DEFAULT_MAX_SANDBOXES;
|
|
20
|
+
this.idleTimeoutMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
21
|
+
this.onBeforeEvict = opts.onBeforeEvict;
|
|
22
|
+
}
|
|
23
|
+
// --- Lifecycle ---
|
|
24
|
+
async init() {
|
|
25
|
+
const marked = await this.db.markAllSandboxesCold();
|
|
26
|
+
if (marked > 0) {
|
|
27
|
+
console.log(`[pool] Startup: marked ${marked} stale sandbox(es) as cold`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async create(opts) {
|
|
31
|
+
// Enforce capacity — evict if needed
|
|
32
|
+
const count = await this.db.countSandboxes();
|
|
33
|
+
if (count >= this.maxCapacity) {
|
|
34
|
+
const evicted = await this.evictOne();
|
|
35
|
+
if (!evicted) {
|
|
36
|
+
throw new Error('Sandbox capacity reached and no evictable sandboxes (all running)');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const sandbox = await this.manager.create(opts);
|
|
40
|
+
// Insert DB row directly as 'warm' — no intermediate warming state needed
|
|
41
|
+
// since the manager.create() call above is the only thing that could fail.
|
|
42
|
+
await this.db.insertSandbox(sandbox.id, opts.agentName, sandbox.workspaceDir, opts.sessionId);
|
|
43
|
+
await this.db.updateSandboxState(sandbox.id, 'warm');
|
|
44
|
+
// Cache in live map
|
|
45
|
+
const entry = {
|
|
46
|
+
sandbox,
|
|
47
|
+
state: 'warm',
|
|
48
|
+
sessionId: opts.sessionId,
|
|
49
|
+
agentName: opts.agentName,
|
|
50
|
+
};
|
|
51
|
+
this.live.set(sandbox.id, entry);
|
|
52
|
+
if (opts.sessionId) {
|
|
53
|
+
this.sessionIndex.set(opts.sessionId, sandbox.id);
|
|
54
|
+
}
|
|
55
|
+
return sandbox;
|
|
56
|
+
}
|
|
57
|
+
get(sandboxId) {
|
|
58
|
+
const entry = this.live.get(sandboxId);
|
|
59
|
+
if (!entry)
|
|
60
|
+
return undefined;
|
|
61
|
+
// Check if process is dead
|
|
62
|
+
if (entry.sandbox.process.exitCode !== null) {
|
|
63
|
+
this.live.delete(sandboxId);
|
|
64
|
+
if (entry.sessionId) {
|
|
65
|
+
this.sessionIndex.delete(entry.sessionId);
|
|
66
|
+
}
|
|
67
|
+
// Fire-and-forget DB update to cold
|
|
68
|
+
this.db.updateSandboxState(sandboxId, 'cold').catch((err) => console.error(`[pool] Failed to mark dead sandbox ${sandboxId} as cold:`, err));
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return entry.sandbox;
|
|
72
|
+
}
|
|
73
|
+
getEntry(sandboxId) {
|
|
74
|
+
return this.live.get(sandboxId);
|
|
75
|
+
}
|
|
76
|
+
getSandboxForSession(sessionId) {
|
|
77
|
+
const sandboxId = this.sessionIndex.get(sessionId);
|
|
78
|
+
if (!sandboxId)
|
|
79
|
+
return undefined;
|
|
80
|
+
return this.get(sandboxId);
|
|
81
|
+
}
|
|
82
|
+
async destroy(sandboxId) {
|
|
83
|
+
const entry = this.live.get(sandboxId);
|
|
84
|
+
// Kill process via manager
|
|
85
|
+
await this.manager.destroy(sandboxId);
|
|
86
|
+
// Remove from in-memory maps
|
|
87
|
+
if (entry) {
|
|
88
|
+
this.live.delete(sandboxId);
|
|
89
|
+
if (entry.sessionId) {
|
|
90
|
+
this.sessionIndex.delete(entry.sessionId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Delete DB row
|
|
94
|
+
await this.db.deleteSandbox(sandboxId);
|
|
95
|
+
}
|
|
96
|
+
async destroyAll() {
|
|
97
|
+
const ids = [...this.live.keys()];
|
|
98
|
+
await Promise.all(ids.map((id) => this.destroy(id)));
|
|
99
|
+
// Also clean cold entries from DB
|
|
100
|
+
// (destroyAll is for full shutdown — clean everything)
|
|
101
|
+
}
|
|
102
|
+
// --- State transitions ---
|
|
103
|
+
markRunning(sandboxId) {
|
|
104
|
+
const entry = this.live.get(sandboxId);
|
|
105
|
+
if (!entry)
|
|
106
|
+
return;
|
|
107
|
+
entry.state = 'running';
|
|
108
|
+
// Fire-and-forget DB update
|
|
109
|
+
this.db.updateSandboxState(sandboxId, 'running').catch((err) => console.error(`[pool] Failed to update sandbox ${sandboxId} state to running:`, err));
|
|
110
|
+
this.db.touchSandbox(sandboxId).catch(() => { });
|
|
111
|
+
}
|
|
112
|
+
markWaiting(sandboxId) {
|
|
113
|
+
const entry = this.live.get(sandboxId);
|
|
114
|
+
if (!entry)
|
|
115
|
+
return;
|
|
116
|
+
entry.state = 'waiting';
|
|
117
|
+
// Fire-and-forget DB update
|
|
118
|
+
this.db.updateSandboxState(sandboxId, 'waiting').catch((err) => console.error(`[pool] Failed to update sandbox ${sandboxId} state to waiting:`, err));
|
|
119
|
+
this.db.touchSandbox(sandboxId).catch(() => { });
|
|
120
|
+
}
|
|
121
|
+
// --- Eviction ---
|
|
122
|
+
async evictOne() {
|
|
123
|
+
const candidate = await this.db.getBestEvictionCandidate();
|
|
124
|
+
if (!candidate)
|
|
125
|
+
return false;
|
|
126
|
+
if (candidate.state === 'cold') {
|
|
127
|
+
// Cold eviction — delete persisted state + DB row
|
|
128
|
+
if (candidate.sessionId) {
|
|
129
|
+
deleteSessionState(this.dataDir, candidate.sessionId);
|
|
130
|
+
deleteCloudState(candidate.sessionId).catch((err) => console.error(`[pool] Cloud delete failed for ${candidate.sessionId}:`, err));
|
|
131
|
+
}
|
|
132
|
+
await this.db.deleteSandbox(candidate.id);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (candidate.state === 'warm') {
|
|
136
|
+
// Warm eviction — kill sandbox (no active session work)
|
|
137
|
+
await this.manager.destroy(candidate.id);
|
|
138
|
+
this.live.delete(candidate.id);
|
|
139
|
+
if (candidate.sessionId) {
|
|
140
|
+
this.sessionIndex.delete(candidate.sessionId);
|
|
141
|
+
}
|
|
142
|
+
await this.db.deleteSandbox(candidate.id);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// Waiting eviction — kill sandbox (idle session), preserve state
|
|
146
|
+
const entry = this.live.get(candidate.id);
|
|
147
|
+
if (entry && this.onBeforeEvict) {
|
|
148
|
+
await this.onBeforeEvict(entry);
|
|
149
|
+
}
|
|
150
|
+
await this.manager.destroy(candidate.id);
|
|
151
|
+
this.live.delete(candidate.id);
|
|
152
|
+
if (candidate.sessionId) {
|
|
153
|
+
this.sessionIndex.delete(candidate.sessionId);
|
|
154
|
+
}
|
|
155
|
+
// Mark cold (session is paused, state persisted by onBeforeEvict)
|
|
156
|
+
await this.db.updateSandboxState(candidate.id, 'cold');
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
// --- Idle sweep ---
|
|
160
|
+
async sweepIdle() {
|
|
161
|
+
const threshold = new Date(Date.now() - this.idleTimeoutMs).toISOString();
|
|
162
|
+
const idleSandboxes = await this.db.getIdleSandboxes(threshold);
|
|
163
|
+
let swept = 0;
|
|
164
|
+
for (const record of idleSandboxes) {
|
|
165
|
+
const entry = this.live.get(record.id);
|
|
166
|
+
if (!entry)
|
|
167
|
+
continue; // not live — skip
|
|
168
|
+
// Evict: persist state, kill, mark cold
|
|
169
|
+
if (this.onBeforeEvict) {
|
|
170
|
+
await this.onBeforeEvict(entry);
|
|
171
|
+
}
|
|
172
|
+
await this.manager.destroy(record.id);
|
|
173
|
+
this.live.delete(record.id);
|
|
174
|
+
if (entry.sessionId) {
|
|
175
|
+
this.sessionIndex.delete(entry.sessionId);
|
|
176
|
+
}
|
|
177
|
+
await this.db.updateSandboxState(record.id, 'cold');
|
|
178
|
+
swept++;
|
|
179
|
+
}
|
|
180
|
+
if (swept > 0) {
|
|
181
|
+
console.log(`[pool] Idle sweep: evicted ${swept} sandbox(es)`);
|
|
182
|
+
}
|
|
183
|
+
return swept;
|
|
184
|
+
}
|
|
185
|
+
startIdleSweep() {
|
|
186
|
+
if (this.sweepTimer)
|
|
187
|
+
return;
|
|
188
|
+
this.sweepTimer = setInterval(() => {
|
|
189
|
+
this.sweepIdle().catch((err) => console.error('[pool] Idle sweep error:', err));
|
|
190
|
+
}, IDLE_SWEEP_INTERVAL_MS);
|
|
191
|
+
// Unref so the timer doesn't keep the process alive
|
|
192
|
+
this.sweepTimer.unref();
|
|
193
|
+
}
|
|
194
|
+
stopIdleSweep() {
|
|
195
|
+
if (this.sweepTimer) {
|
|
196
|
+
clearInterval(this.sweepTimer);
|
|
197
|
+
this.sweepTimer = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// --- Resume metrics ---
|
|
201
|
+
recordWarmHit() { this.resumeWarmHits++; }
|
|
202
|
+
recordColdHit() { this.resumeColdHits++; }
|
|
203
|
+
// --- Stats ---
|
|
204
|
+
get stats() {
|
|
205
|
+
// Count live states from in-memory map
|
|
206
|
+
let warming = 0, warm = 0, waiting = 0, running = 0;
|
|
207
|
+
for (const entry of this.live.values()) {
|
|
208
|
+
switch (entry.state) {
|
|
209
|
+
case 'warming':
|
|
210
|
+
warming++;
|
|
211
|
+
break;
|
|
212
|
+
case 'warm':
|
|
213
|
+
warm++;
|
|
214
|
+
break;
|
|
215
|
+
case 'waiting':
|
|
216
|
+
waiting++;
|
|
217
|
+
break;
|
|
218
|
+
case 'running':
|
|
219
|
+
running++;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// We can't synchronously get cold count from DB, so we compute total from what we know.
|
|
224
|
+
// For accurate cold count, use statsAsync().
|
|
225
|
+
return {
|
|
226
|
+
total: this.live.size, // live only — call statsAsync for full count
|
|
227
|
+
cold: 0,
|
|
228
|
+
warming,
|
|
229
|
+
warm,
|
|
230
|
+
waiting,
|
|
231
|
+
running,
|
|
232
|
+
maxCapacity: this.maxCapacity,
|
|
233
|
+
resumeWarmHits: this.resumeWarmHits,
|
|
234
|
+
resumeColdHits: this.resumeColdHits,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async statsAsync() {
|
|
238
|
+
const baseStats = this.stats;
|
|
239
|
+
const totalDb = await this.db.countSandboxes();
|
|
240
|
+
const cold = totalDb - this.live.size;
|
|
241
|
+
return {
|
|
242
|
+
...baseStats,
|
|
243
|
+
total: totalDb,
|
|
244
|
+
cold: Math.max(0, cold),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
get activeCount() {
|
|
248
|
+
return this.live.size;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=pool.js.map
|
package/dist/pool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ChildProcess, type SpawnOptions } from 'node:child_process';
|
|
2
|
+
import type { SandboxLimits } from '@ash-ai/shared';
|
|
3
|
+
import { DEFAULT_SANDBOX_LIMITS } from '@ash-ai/shared';
|
|
4
|
+
export { DEFAULT_SANDBOX_LIMITS };
|
|
5
|
+
export interface SpawnResult {
|
|
6
|
+
child: ChildProcess;
|
|
7
|
+
cleanup: () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface SandboxSpawnOpts {
|
|
10
|
+
sandboxId: string;
|
|
11
|
+
workspaceDir: string;
|
|
12
|
+
agentDir: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createCgroup(sandboxId: string, limits: SandboxLimits): string;
|
|
15
|
+
export declare function addToCgroup(cgroupPath: string, pid: number): void;
|
|
16
|
+
export declare function removeCgroup(cgroupPath: string): void;
|
|
17
|
+
export declare function spawnWithLimits(command: string, args: string[], opts: SpawnOptions, limits: SandboxLimits, sandboxOpts: SandboxSpawnOpts): SpawnResult;
|
|
18
|
+
export declare function isOomExit(code: number | null, signal: string | null): boolean;
|
|
19
|
+
export declare function getDirSizeKb(dir: string): number;
|
|
20
|
+
export declare function startDiskMonitor(workspaceDir: string, limitMb: number, onExceeded: () => void, intervalMs?: number): NodeJS.Timeout;
|
|
21
|
+
//# sourceMappingURL=resource-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-limits.d.ts","sourceRoot":"","sources":["../src/resource-limits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG3F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAA0B,MAAM,gBAAgB,CAAC;AAEhF,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAMlC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAwBD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAqB7E;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAEjE;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAMrD;AA6CD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,gBAAgB,GAC5B,WAAW,CAOb;AAmCD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAE7E;AAMD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,IAAI,EACtB,UAAU,GAAE,MAA+B,GAC1C,MAAM,CAAC,OAAO,CAWhB"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { execSync, spawn } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { DEFAULT_SANDBOX_LIMITS, DISK_CHECK_INTERVAL_MS } from '@ash-ai/shared';
|
|
5
|
+
export { DEFAULT_SANDBOX_LIMITS };
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Platform detection
|
|
8
|
+
// =============================================================================
|
|
9
|
+
function hasCgroups() {
|
|
10
|
+
if (process.platform !== 'linux')
|
|
11
|
+
return false;
|
|
12
|
+
try {
|
|
13
|
+
// Check that the ash cgroup parent exists and is writable
|
|
14
|
+
// (docker-entrypoint.sh sets this up, or it exists on native Linux with proper perms)
|
|
15
|
+
execSync('test -d /sys/fs/cgroup/ash && test -w /sys/fs/cgroup/ash', { stdio: 'ignore' });
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Linux: cgroups v2
|
|
24
|
+
// =============================================================================
|
|
25
|
+
const CGROUP_ROOT = '/sys/fs/cgroup/ash';
|
|
26
|
+
export function createCgroup(sandboxId, limits) {
|
|
27
|
+
const cgroupPath = join(CGROUP_ROOT, sandboxId);
|
|
28
|
+
mkdirSync(cgroupPath, { recursive: true });
|
|
29
|
+
// Memory limit
|
|
30
|
+
const memoryBytes = limits.memoryMb * 1024 * 1024;
|
|
31
|
+
writeFileSync(join(cgroupPath, 'memory.max'), String(memoryBytes));
|
|
32
|
+
try {
|
|
33
|
+
writeFileSync(join(cgroupPath, 'memory.swap.max'), '0');
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// swap controller may not be enabled
|
|
37
|
+
}
|
|
38
|
+
// CPU limit (100000 period, quota scales with cpuPercent)
|
|
39
|
+
const cpuQuota = limits.cpuPercent * 1000;
|
|
40
|
+
writeFileSync(join(cgroupPath, 'cpu.max'), `${cpuQuota} 100000`);
|
|
41
|
+
// Process limit (fork bomb protection)
|
|
42
|
+
writeFileSync(join(cgroupPath, 'pids.max'), String(limits.maxProcesses));
|
|
43
|
+
return cgroupPath;
|
|
44
|
+
}
|
|
45
|
+
export function addToCgroup(cgroupPath, pid) {
|
|
46
|
+
writeFileSync(join(cgroupPath, 'cgroup.procs'), String(pid));
|
|
47
|
+
}
|
|
48
|
+
export function removeCgroup(cgroupPath) {
|
|
49
|
+
try {
|
|
50
|
+
rmSync(cgroupPath, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// may already be gone or processes still inside
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Fallback: ulimit (macOS dev without Docker)
|
|
58
|
+
// =============================================================================
|
|
59
|
+
function buildUlimitPrefix(limits) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
// ulimit -v (virtual memory) doesn't work on macOS — skip it there
|
|
62
|
+
if (process.platform !== 'darwin') {
|
|
63
|
+
parts.push(`ulimit -v ${limits.memoryMb * 1024}`);
|
|
64
|
+
}
|
|
65
|
+
// ulimit -u (max user processes) — on macOS this applies to the entire user,
|
|
66
|
+
// not just the subprocess tree. Setting it to 64 when the user already has 60+
|
|
67
|
+
// processes causes "fork: Resource temporarily unavailable". Skip on macOS.
|
|
68
|
+
if (process.platform !== 'darwin') {
|
|
69
|
+
parts.push(`ulimit -u ${limits.maxProcesses}`);
|
|
70
|
+
}
|
|
71
|
+
parts.push(`ulimit -f ${limits.diskMb * 1024}`); // Max file size (KB blocks)
|
|
72
|
+
return parts.join(' && ');
|
|
73
|
+
}
|
|
74
|
+
function spawnWithUlimit(command, args, opts, limits) {
|
|
75
|
+
const prefix = buildUlimitPrefix(limits);
|
|
76
|
+
const escapedArgs = args.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
|
|
77
|
+
// Use bash (not sh) — Debian's sh is dash which doesn't support ulimit -u
|
|
78
|
+
const shell = process.platform === 'linux' ? 'bash' : 'sh';
|
|
79
|
+
const child = spawn(shell, ['-c', `${prefix} && exec ${command} ${escapedArgs}`], opts);
|
|
80
|
+
return { child, cleanup: () => { } };
|
|
81
|
+
}
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Unified spawn
|
|
84
|
+
// =============================================================================
|
|
85
|
+
export function spawnWithLimits(command, args, opts, limits, sandboxOpts) {
|
|
86
|
+
if (hasCgroups()) {
|
|
87
|
+
return spawnWithCgroups(command, args, opts, limits, sandboxOpts);
|
|
88
|
+
}
|
|
89
|
+
// macOS or unprivileged Linux — ulimit fallback
|
|
90
|
+
return spawnWithUlimit(command, args, opts, limits);
|
|
91
|
+
}
|
|
92
|
+
function spawnWithCgroups(command, args, opts, limits, sandboxOpts) {
|
|
93
|
+
let cgroupPath;
|
|
94
|
+
try {
|
|
95
|
+
cgroupPath = createCgroup(sandboxOpts.sandboxId, limits);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error(`[resource-limits] cgroups available but failed to create: ${err}`);
|
|
99
|
+
return spawnWithUlimit(command, args, opts, limits);
|
|
100
|
+
}
|
|
101
|
+
const child = spawn(command, args, opts);
|
|
102
|
+
if (child.pid) {
|
|
103
|
+
try {
|
|
104
|
+
addToCgroup(cgroupPath, child.pid);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error(`[resource-limits] Failed to add PID ${child.pid} to cgroup: ${err}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const cleanup = () => removeCgroup(cgroupPath);
|
|
111
|
+
return { child, cleanup };
|
|
112
|
+
}
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// OOM detection
|
|
115
|
+
// =============================================================================
|
|
116
|
+
export function isOomExit(code, signal) {
|
|
117
|
+
return signal === 'SIGKILL' || code === 137;
|
|
118
|
+
}
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// Disk usage monitoring
|
|
121
|
+
// =============================================================================
|
|
122
|
+
export function getDirSizeKb(dir) {
|
|
123
|
+
const output = execSync(`du -sk '${dir}'`, { timeout: 5000 }).toString().trim();
|
|
124
|
+
return parseInt(output.split('\t')[0], 10);
|
|
125
|
+
}
|
|
126
|
+
export function startDiskMonitor(workspaceDir, limitMb, onExceeded, intervalMs = DISK_CHECK_INTERVAL_MS) {
|
|
127
|
+
return setInterval(() => {
|
|
128
|
+
try {
|
|
129
|
+
const sizeKb = getDirSizeKb(workspaceDir);
|
|
130
|
+
if (sizeKb > limitMb * 1024) {
|
|
131
|
+
onExceeded();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// du failed — workspace may be gone
|
|
136
|
+
}
|
|
137
|
+
}, intervalMs);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=resource-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-limits.js","sourceRoot":"","sources":["../src/resource-limits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SnapshotStore } from './snapshot-store.js';
|
|
2
|
+
export declare class GcsSnapshotStore implements SnapshotStore {
|
|
3
|
+
private storage;
|
|
4
|
+
private bucket;
|
|
5
|
+
private prefix;
|
|
6
|
+
constructor(bucket: string, prefix: string);
|
|
7
|
+
private key;
|
|
8
|
+
upload(sessionId: string, tarPath: string): Promise<boolean>;
|
|
9
|
+
download(sessionId: string, destPath: string): Promise<boolean>;
|
|
10
|
+
exists(sessionId: string): Promise<boolean>;
|
|
11
|
+
delete(sessionId: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=snapshot-gcs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-gcs.d.ts","sourceRoot":"","sources":["../src/snapshot-gcs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,qBAAa,gBAAiB,YAAW,aAAa;IACpD,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAM1C,OAAO,CAAC,GAAG;IAIL,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAY5D,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc/D,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU3C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ/C"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Storage } from '@google-cloud/storage';
|
|
2
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
export class GcsSnapshotStore {
|
|
5
|
+
storage;
|
|
6
|
+
bucket;
|
|
7
|
+
prefix;
|
|
8
|
+
constructor(bucket, prefix) {
|
|
9
|
+
this.bucket = bucket;
|
|
10
|
+
this.prefix = prefix;
|
|
11
|
+
this.storage = new Storage(); // Uses ADC (Application Default Credentials)
|
|
12
|
+
}
|
|
13
|
+
key(sessionId) {
|
|
14
|
+
return `${this.prefix}${sessionId}/workspace.tar.gz`;
|
|
15
|
+
}
|
|
16
|
+
async upload(sessionId, tarPath) {
|
|
17
|
+
try {
|
|
18
|
+
const file = this.storage.bucket(this.bucket).file(this.key(sessionId));
|
|
19
|
+
const stream = createReadStream(tarPath);
|
|
20
|
+
await pipeline(stream, file.createWriteStream({ contentType: 'application/gzip' }));
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error(`[snapshot-gcs] Upload failed for ${sessionId}:`, err);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async download(sessionId, destPath) {
|
|
29
|
+
try {
|
|
30
|
+
const file = this.storage.bucket(this.bucket).file(this.key(sessionId));
|
|
31
|
+
const [exists] = await file.exists();
|
|
32
|
+
if (!exists)
|
|
33
|
+
return false;
|
|
34
|
+
const ws = createWriteStream(destPath);
|
|
35
|
+
await pipeline(file.createReadStream(), ws);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error(`[snapshot-gcs] Download failed for ${sessionId}:`, err);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async exists(sessionId) {
|
|
44
|
+
try {
|
|
45
|
+
const file = this.storage.bucket(this.bucket).file(this.key(sessionId));
|
|
46
|
+
const [exists] = await file.exists();
|
|
47
|
+
return exists;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async delete(sessionId) {
|
|
54
|
+
try {
|
|
55
|
+
const file = this.storage.bucket(this.bucket).file(this.key(sessionId));
|
|
56
|
+
await file.delete({ ignoreNotFound: true });
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.error(`[snapshot-gcs] Delete failed for ${sessionId}:`, err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=snapshot-gcs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-gcs.js","sourceRoot":"","sources":["../src/snapshot-gcs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SnapshotStore } from './snapshot-store.js';
|
|
2
|
+
export declare class S3SnapshotStore implements SnapshotStore {
|
|
3
|
+
private client;
|
|
4
|
+
private bucket;
|
|
5
|
+
private prefix;
|
|
6
|
+
constructor(bucket: string, prefix: string, region?: string);
|
|
7
|
+
private key;
|
|
8
|
+
upload(sessionId: string, tarPath: string): Promise<boolean>;
|
|
9
|
+
download(sessionId: string, destPath: string): Promise<boolean>;
|
|
10
|
+
exists(sessionId: string): Promise<boolean>;
|
|
11
|
+
delete(sessionId: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=snapshot-s3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-s3.d.ts","sourceRoot":"","sources":["../src/snapshot-s3.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,qBAAa,eAAgB,YAAW,aAAa;IACnD,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAM3D,OAAO,CAAC,GAAG;IAIL,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB5D,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiB/D,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAY3C,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAU/C"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, HeadObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
export class S3SnapshotStore {
|
|
5
|
+
client;
|
|
6
|
+
bucket;
|
|
7
|
+
prefix;
|
|
8
|
+
constructor(bucket, prefix, region) {
|
|
9
|
+
this.bucket = bucket;
|
|
10
|
+
this.prefix = prefix;
|
|
11
|
+
this.client = new S3Client({ region: region || process.env.ASH_S3_REGION || 'us-east-1' });
|
|
12
|
+
}
|
|
13
|
+
key(sessionId) {
|
|
14
|
+
return `${this.prefix}${sessionId}/workspace.tar.gz`;
|
|
15
|
+
}
|
|
16
|
+
async upload(sessionId, tarPath) {
|
|
17
|
+
try {
|
|
18
|
+
const stream = createReadStream(tarPath);
|
|
19
|
+
await this.client.send(new PutObjectCommand({
|
|
20
|
+
Bucket: this.bucket,
|
|
21
|
+
Key: this.key(sessionId),
|
|
22
|
+
Body: stream,
|
|
23
|
+
ContentType: 'application/gzip',
|
|
24
|
+
}));
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`[snapshot-s3] Upload failed for ${sessionId}:`, err);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async download(sessionId, destPath) {
|
|
33
|
+
try {
|
|
34
|
+
const resp = await this.client.send(new GetObjectCommand({
|
|
35
|
+
Bucket: this.bucket,
|
|
36
|
+
Key: this.key(sessionId),
|
|
37
|
+
}));
|
|
38
|
+
if (!resp.Body)
|
|
39
|
+
return false;
|
|
40
|
+
const ws = createWriteStream(destPath);
|
|
41
|
+
await pipeline(resp.Body, ws);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err instanceof Error && err.name === 'NoSuchKey')
|
|
46
|
+
return false;
|
|
47
|
+
console.error(`[snapshot-s3] Download failed for ${sessionId}:`, err);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async exists(sessionId) {
|
|
52
|
+
try {
|
|
53
|
+
await this.client.send(new HeadObjectCommand({
|
|
54
|
+
Bucket: this.bucket,
|
|
55
|
+
Key: this.key(sessionId),
|
|
56
|
+
}));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async delete(sessionId) {
|
|
64
|
+
try {
|
|
65
|
+
await this.client.send(new DeleteObjectCommand({
|
|
66
|
+
Bucket: this.bucket,
|
|
67
|
+
Key: this.key(sessionId),
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error(`[snapshot-s3] Delete failed for ${sessionId}:`, err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=snapshot-s3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-s3.js","sourceRoot":"","sources":["../src/snapshot-s3.ts"],"names":[],"mappings":"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"}
|