Skip to content

Commit 009d52b

Browse files
committed
Release v0.2.35
1 parent 80b6796 commit 009d52b

25 files changed

Lines changed: 710 additions & 59 deletions

‎RELEASE.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"version": "0.2.34",
2+
"version": "0.2.35",
33
"license": "FSL-1.1-ALv2",
44
"baselineSha256": "7847f65a9b20b366a60d52d19ca3cd6bdd1bb09b8cb506659a270f27a21d8877",
55
"e2b": {
66
"recipeSha256": "761809ee2efa2b6c750451ace29283cbb8e55b6ce3b5b14c2bb4509df71df801",
77
"profileSha256": "414dbecee2d47de36016ea05c736e2edff3a0deb298babf923887ea184a01c10"
88
},
99
"dxd": {
10-
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.34/dxd-linux-x64",
10+
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.35/dxd-linux-x64",
1111
"sha256": "d3a85385666b547620a62444b8267b84d484e551266e5b272b0ab3d7646b1455"
1212
},
1313
"toolchain": {

‎apps/core/migration-manifest.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@
8282
"filename": "0014_release_v0.2.34.sql",
8383
"sha256": "125cb49c518f6ec42ae01909b79b23953eed585f46f9c4a11f666991519c5200",
8484
"classification": "non-destructive"
85+
},
86+
{
87+
"filename": "0015_release_v0.2.35.sql",
88+
"sha256": "3182fb90f0de5538a69b13b76c566df445d41223e56339bd37cee531bcbcea41",
89+
"classification": "non-destructive"
8590
}
8691
],
8792
"durableObjects": [
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-- A Thread family (a root Thread and its children) now shares as one unit:
2+
-- sharing or unsharing any Thread in it applies to all of them, and a new
3+
-- child starts with its family's sharing. Families shared before this had
4+
-- only the Thread the owner shared; give every Thread in such a family the
5+
-- most permissive sharing any of them has, so nobody loses access.
6+
UPDATE threads
7+
SET (workspace_access, shared_workspace_id, contribute_until, visibility) = (
8+
SELECT source.workspace_access, source.shared_workspace_id,
9+
source.contribute_until, 'workspace'
10+
FROM threads AS source
11+
WHERE (source.id = COALESCE(threads.parent_thread_id, threads.id)
12+
OR source.parent_thread_id = COALESCE(threads.parent_thread_id, threads.id))
13+
AND source.owner_user_id = threads.owner_user_id
14+
AND source.lifecycle_state != 'deleted'
15+
AND source.workspace_access != 'none'
16+
ORDER BY source.workspace_access = 'contribute' DESC,
17+
source.contribute_until DESC,
18+
source.parent_thread_id IS NULL DESC
19+
LIMIT 1)
20+
WHERE lifecycle_state != 'deleted'
21+
AND EXISTS (
22+
SELECT 1 FROM threads AS source
23+
WHERE (source.id = COALESCE(threads.parent_thread_id, threads.id)
24+
OR source.parent_thread_id = COALESCE(threads.parent_thread_id, threads.id))
25+
AND source.owner_user_id = threads.owner_user_id
26+
AND source.lifecycle_state != 'deleted'
27+
AND source.workspace_access != 'none');

‎apps/core/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@dx/core",
33
"private": true,
4-
"version": "0.2.34",
4+
"version": "0.2.35",
55
"license": "FSL-1.1-ALv2",
66
"type": "module",
77
"scripts": {

‎apps/core/src/execution/cloudflare/account-orb-worker.generated.js‎

Lines changed: 81 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎apps/core/src/execution/cloudflare/orb-container.ts‎

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ const MAX_CONTAINER_INACTIVITY_MS = 6 * 60 * 60 * 1000;
154154
const CONTAINER_INACTIVITY_MARGIN_MS = 15 * 60 * 1000;
155155
const READY_TIMEOUT_MS = 90_000;
156156
const READY_RETRY_MS = 100;
157+
/** While a start waits for readiness, log its progress this often. */
158+
const READY_PROGRESS_LOG_MS = 30_000;
157159
/** A command's output kept in this object's memory, per stream. */
158160
const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
159161
/** The largest file read in one call (one RPC result). */
@@ -322,18 +324,77 @@ export class OrbContainerController {
322324
);
323325
}
324326

325-
/** The container accepts `exec` once it has started. */
327+
/**
328+
* The container accepts `exec` once it has started. Logs progress while it
329+
* waits, so a stalled start shows whether one probe hung
330+
* (`currentAttemptMs`) or probes kept failing (`attempts`, `lastFailure`).
331+
*/
326332
async #waitReady(operation: string) {
327-
const deadline = this.#now() + READY_TIMEOUT_MS;
333+
const startedAt = this.#now();
334+
const deadline = startedAt + READY_TIMEOUT_MS;
328335
let lastFailure: unknown;
329-
while (this.#now() < deadline) {
330-
try {
331-
const process = await this.#container.exec(["true"]);
332-
if ((await process.exitCode) === 0) return;
333-
} catch (cause) {
334-
lastFailure = cause;
336+
let attempts = 0;
337+
let attemptStartedAt = startedAt;
338+
let slowestAttemptMs = 0;
339+
const progress = (outcome: "waiting" | "ready" | "timed_out") => ({
340+
event: "orb_container_ready_wait",
341+
outcome,
342+
sandboxId: this.#sandboxId,
343+
operation,
344+
elapsedMs: this.#now() - startedAt,
345+
attempts,
346+
slowestAttemptMs,
347+
currentAttemptMs: this.#now() - attemptStartedAt,
348+
running: this.#container.running,
349+
...(lastFailure === undefined
350+
? {}
351+
: {
352+
lastFailure:
353+
lastFailure instanceof Error
354+
? lastFailure.message
355+
: String(lastFailure),
356+
}),
357+
});
358+
const watchdog = setInterval(
359+
() =>
360+
orbContainerLogger.warn(
361+
"Orb container is not ready yet.",
362+
progress("waiting"),
363+
),
364+
READY_PROGRESS_LOG_MS,
365+
);
366+
try {
367+
while (this.#now() < deadline) {
368+
attempts += 1;
369+
attemptStartedAt = this.#now();
370+
try {
371+
const process = await this.#container.exec(["true"]);
372+
if ((await process.exitCode) === 0) {
373+
slowestAttemptMs = Math.max(
374+
slowestAttemptMs,
375+
this.#now() - attemptStartedAt,
376+
);
377+
orbContainerLogger.info(
378+
"Orb container is ready.",
379+
progress("ready"),
380+
);
381+
return;
382+
}
383+
} catch (cause) {
384+
lastFailure = cause;
385+
}
386+
slowestAttemptMs = Math.max(
387+
slowestAttemptMs,
388+
this.#now() - attemptStartedAt,
389+
);
390+
await sleep(READY_RETRY_MS);
335391
}
336-
await sleep(READY_RETRY_MS);
392+
orbContainerLogger.error(
393+
"Orb container did not become ready.",
394+
progress("timed_out"),
395+
);
396+
} finally {
397+
clearInterval(watchdog);
337398
}
338399
throw failure(
339400
operation,
@@ -349,8 +410,51 @@ export class OrbContainerController {
349410
state: OrbWorkspaceState,
350411
idleMs: number,
351412
fresh = false,
413+
lockWaitMs = 0,
352414
): Promise<"paused" | "created"> {
353415
const now = this.#now();
416+
const snapshot = state.snapshot;
417+
const details = {
418+
event: "orb_container_start",
419+
sandboxId: this.#sandboxId,
420+
source: snapshot === undefined ? "image" : "snapshot",
421+
...(snapshot === undefined
422+
? {}
423+
: {
424+
snapshotId: snapshot.id,
425+
snapshotSize: snapshot.size,
426+
snapshotAgeMs: now - snapshot.refreshedAt,
427+
}),
428+
instance: state.instance,
429+
fresh,
430+
lockWaitMs,
431+
};
432+
orbContainerLogger.info("Orb container start requested.", details);
433+
try {
434+
const residency = await this.#startContainer(state, idleMs, fresh, now);
435+
orbContainerLogger.info("Orb container started.", {
436+
...details,
437+
outcome: "started",
438+
durationMs: this.#now() - now,
439+
});
440+
return residency;
441+
} catch (cause) {
442+
orbContainerLogger.error("Orb container start failed.", {
443+
...details,
444+
outcome: "error",
445+
durationMs: this.#now() - now,
446+
error: cause instanceof Error ? cause.message : String(cause),
447+
});
448+
throw cause;
449+
}
450+
}
451+
452+
async #startContainer(
453+
state: OrbWorkspaceState,
454+
idleMs: number,
455+
fresh: boolean,
456+
now: number,
457+
): Promise<"paused" | "created"> {
354458
const snapshot = state.snapshot;
355459
if (snapshot === undefined) {
356460
const image = this.#image();
@@ -401,7 +505,9 @@ export class OrbContainerController {
401505
readonly instance: CloudflareContainerInstance;
402506
readonly idleMs: number;
403507
}): Promise<OrbOpenResult> {
508+
const requestedAt = this.#now();
404509
return this.#exclusive(async () => {
510+
const lockWaitMs = this.#now() - requestedAt;
405511
let state = await this.#load();
406512
let fresh = false;
407513
if (state === undefined) {
@@ -438,7 +544,12 @@ export class OrbContainerController {
438544
: { processesStartedAt: state.startedAt }),
439545
};
440546
}
441-
const residency = await this.#start(state, input.idleMs, fresh);
547+
const residency = await this.#start(
548+
state,
549+
input.idleMs,
550+
fresh,
551+
lockWaitMs,
552+
);
442553
const started = await this.#load();
443554
return {
444555
kind: "ready",

0 commit comments

Comments
 (0)