@molecule/api-code-sandbox-flyio 1.2.9 → 1.2.10
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/README.md +43 -13
- package/dist/provider.d.ts +38 -11
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +116 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-16T21:44:23.560Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-code-sandbox-flyio
|
|
@@ -1527,28 +1527,58 @@ function isFailedState(state: FlyMachineState): boolean
|
|
|
1527
1527
|
|
|
1528
1528
|
**Returns:** `true` for `failed` and `launch_failed`.
|
|
1529
1529
|
|
|
1530
|
-
#### `isTransientExecPrecondition(error)`
|
|
1530
|
+
#### `isTransientExecPrecondition(error, machineState)`
|
|
1531
1531
|
|
|
1532
|
-
Whether a failed exec is Fly's TRANSIENT precondition —
|
|
1533
|
-
|
|
1534
|
-
ran (`412 failed_precondition: exec request failed: EOF`) — rather than the
|
|
1535
|
-
Machine being in a state an exec cannot run in.
|
|
1532
|
+
Whether a failed exec is Fly's TRANSIENT precondition — one a repeat (after a
|
|
1533
|
+
short wait) can clear, rather than a Machine state the exec has to wait out.
|
|
1536
1534
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1535
|
+
Fly answers the exec endpoint with 412 in two different situations, and only
|
|
1536
|
+
the BODY and the Machine's own reported state separate them:
|
|
1537
|
+
|
|
1538
|
+
1. `failed_precondition: exec request failed: EOF` — the request reached the
|
|
1539
|
+
Machine's exec agent and the connection dropped before the command ran.
|
|
1540
|
+
Always transient: the Machine is `started` and its agent recovers in
|
|
1541
|
+
seconds.
|
|
1542
|
+
2. `failed_precondition: machine not running` — transient ONLY while Fly's
|
|
1543
|
+
own Machines API reports the Machine `started`, `starting` or `replacing`
|
|
1544
|
+
(`machineState`). The exec endpoint lags the state Fly reports: a Machine
|
|
1545
|
+
whose config or image was just updated, or that is seconds past boot,
|
|
1546
|
+
reads `started` in `GET /machines/{id}` while its exec endpoint still
|
|
1547
|
+
refuses requests. Against a Machine Fly reports `stopped`/`suspended`/
|
|
1548
|
+
`destroyed` the same body is the REAL precondition — the Machine is not
|
|
1549
|
+
running, and {@link FlyioSandboxProvider.startMachine} is what answers it;
|
|
1550
|
+
repeating the exec would only burn the caller's budget.
|
|
1543
1551
|
|
|
1544
1552
|
```typescript
|
|
1545
|
-
function isTransientExecPrecondition(error: unknown): boolean
|
|
1553
|
+
function isTransientExecPrecondition(error: unknown, machineState?: string | null): boolean
|
|
1546
1554
|
```
|
|
1547
1555
|
|
|
1548
1556
|
- `error` — The failure an exec attempt raised.
|
|
1557
|
+
- `machineState` — The state `GET /machines/{id}` reports for the Machine, or `null`/`undefined` when it could not be read (which classifies case 2 as NOT transient — an unknown state is never assumed recoverable).
|
|
1549
1558
|
|
|
1550
1559
|
**Returns:** `true` when repeating the exec is the right recovery.
|
|
1551
1560
|
|
|
1561
|
+
#### `isMachineWarmingUp(state)`
|
|
1562
|
+
|
|
1563
|
+
Whether a state Fly reports for a Machine means the Machine is up, or on its
|
|
1564
|
+
way up — the states in which an exec that was refused for `machine not
|
|
1565
|
+
running` is the exec endpoint lagging the state, not the truth.
|
|
1566
|
+
|
|
1567
|
+
A deliberate allow-list rather than "anything but stopped/suspended": the
|
|
1568
|
+
three states here are the ones a refused exec was observed to recover from,
|
|
1569
|
+
and every other state — `stopped`, `suspended`, `destroyed`, `failed`,
|
|
1570
|
+
`created`, unreadable — keeps today's immediate throw. Widening it without
|
|
1571
|
+
that evidence would turn a real precondition into a retry budget spent
|
|
1572
|
+
against a Machine that is not coming up.
|
|
1573
|
+
|
|
1574
|
+
```typescript
|
|
1575
|
+
function isMachineWarmingUp(state: string | null | undefined): boolean
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
- `state` — The Machine state from `GET /machines/{id}`, if readable.
|
|
1579
|
+
|
|
1580
|
+
**Returns:** `true` when waiting and repeating the exec can clear the refusal.
|
|
1581
|
+
|
|
1552
1582
|
#### `createProvider(config, client, store)`
|
|
1553
1583
|
|
|
1554
1584
|
Creates a Fly.io Machines sandbox provider.
|
package/dist/provider.d.ts
CHANGED
|
@@ -11,22 +11,49 @@ import { FlyApiClient } from './api.js';
|
|
|
11
11
|
import { type ObjectStore } from './storage.js';
|
|
12
12
|
import type { FlyioConfig } from './types.js';
|
|
13
13
|
/**
|
|
14
|
-
* Whether a failed exec is Fly's TRANSIENT precondition —
|
|
15
|
-
*
|
|
16
|
-
* ran (`412 failed_precondition: exec request failed: EOF`) — rather than the
|
|
17
|
-
* Machine being in a state an exec cannot run in.
|
|
14
|
+
* Whether a failed exec is Fly's TRANSIENT precondition — one a repeat (after a
|
|
15
|
+
* short wait) can clear, rather than a Machine state the exec has to wait out.
|
|
18
16
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
17
|
+
* Fly answers the exec endpoint with 412 in two different situations, and only
|
|
18
|
+
* the BODY and the Machine's own reported state separate them:
|
|
19
|
+
*
|
|
20
|
+
* 1. `failed_precondition: exec request failed: EOF` — the request reached the
|
|
21
|
+
* Machine's exec agent and the connection dropped before the command ran.
|
|
22
|
+
* Always transient: the Machine is `started` and its agent recovers in
|
|
23
|
+
* seconds.
|
|
24
|
+
* 2. `failed_precondition: machine not running` — transient ONLY while Fly's
|
|
25
|
+
* own Machines API reports the Machine `started`, `starting` or `replacing`
|
|
26
|
+
* (`machineState`). The exec endpoint lags the state Fly reports: a Machine
|
|
27
|
+
* whose config or image was just updated, or that is seconds past boot,
|
|
28
|
+
* reads `started` in `GET /machines/{id}` while its exec endpoint still
|
|
29
|
+
* refuses requests. Against a Machine Fly reports `stopped`/`suspended`/
|
|
30
|
+
* `destroyed` the same body is the REAL precondition — the Machine is not
|
|
31
|
+
* running, and {@link FlyioSandboxProvider.startMachine} is what answers it;
|
|
32
|
+
* repeating the exec would only burn the caller's budget.
|
|
25
33
|
*
|
|
26
34
|
* @param error - The failure an exec attempt raised.
|
|
35
|
+
* @param machineState - The state `GET /machines/{id}` reports for the Machine,
|
|
36
|
+
* or `null`/`undefined` when it could not be read (which classifies case 2 as
|
|
37
|
+
* NOT transient — an unknown state is never assumed recoverable).
|
|
27
38
|
* @returns `true` when repeating the exec is the right recovery.
|
|
28
39
|
*/
|
|
29
|
-
export declare function isTransientExecPrecondition(error: unknown): boolean;
|
|
40
|
+
export declare function isTransientExecPrecondition(error: unknown, machineState?: string | null): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a state Fly reports for a Machine means the Machine is up, or on its
|
|
43
|
+
* way up — the states in which an exec that was refused for `machine not
|
|
44
|
+
* running` is the exec endpoint lagging the state, not the truth.
|
|
45
|
+
*
|
|
46
|
+
* A deliberate allow-list rather than "anything but stopped/suspended": the
|
|
47
|
+
* three states here are the ones a refused exec was observed to recover from,
|
|
48
|
+
* and every other state — `stopped`, `suspended`, `destroyed`, `failed`,
|
|
49
|
+
* `created`, unreadable — keeps today's immediate throw. Widening it without
|
|
50
|
+
* that evidence would turn a real precondition into a retry budget spent
|
|
51
|
+
* against a Machine that is not coming up.
|
|
52
|
+
*
|
|
53
|
+
* @param state - The Machine state from `GET /machines/{id}`, if readable.
|
|
54
|
+
* @returns `true` when waiting and repeating the exec can clear the refusal.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isMachineWarmingUp(state: string | null | undefined): boolean;
|
|
30
57
|
/**
|
|
31
58
|
* Creates a Fly.io Machines sandbox provider.
|
|
32
59
|
* @param config - Optional Fly configuration: API token/URL, org slug, app
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAWV,eAAe,EAGhB,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAAE,YAAY,EAAgC,MAAM,UAAU,CAAA;AAwBrE,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAEhG,OAAO,KAAK,EAIV,WAAW,EAUZ,MAAM,YAAY,CAAA;AAkBnB
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAWV,eAAe,EAGhB,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAAE,YAAY,EAAgC,MAAM,UAAU,CAAA;AAwBrE,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAEhG,OAAO,KAAK,EAIV,WAAW,EAUZ,MAAM,YAAY,CAAA;AAkBnB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIjG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE5E;AAksFD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,YAAY,EACrB,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,GACzB,eAAe,CAEjB"}
|
package/dist/provider.js
CHANGED
|
@@ -32,25 +32,56 @@ function describeDestroyFailure(step, id, error) {
|
|
|
32
32
|
return `Fly sandbox destroy failed at the ${step} for ${id} (${status}): ${reason}`;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Whether a failed exec is Fly's TRANSIENT precondition —
|
|
36
|
-
*
|
|
37
|
-
* ran (`412 failed_precondition: exec request failed: EOF`) — rather than the
|
|
38
|
-
* Machine being in a state an exec cannot run in.
|
|
35
|
+
* Whether a failed exec is Fly's TRANSIENT precondition — one a repeat (after a
|
|
36
|
+
* short wait) can clear, rather than a Machine state the exec has to wait out.
|
|
39
37
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
38
|
+
* Fly answers the exec endpoint with 412 in two different situations, and only
|
|
39
|
+
* the BODY and the Machine's own reported state separate them:
|
|
40
|
+
*
|
|
41
|
+
* 1. `failed_precondition: exec request failed: EOF` — the request reached the
|
|
42
|
+
* Machine's exec agent and the connection dropped before the command ran.
|
|
43
|
+
* Always transient: the Machine is `started` and its agent recovers in
|
|
44
|
+
* seconds.
|
|
45
|
+
* 2. `failed_precondition: machine not running` — transient ONLY while Fly's
|
|
46
|
+
* own Machines API reports the Machine `started`, `starting` or `replacing`
|
|
47
|
+
* (`machineState`). The exec endpoint lags the state Fly reports: a Machine
|
|
48
|
+
* whose config or image was just updated, or that is seconds past boot,
|
|
49
|
+
* reads `started` in `GET /machines/{id}` while its exec endpoint still
|
|
50
|
+
* refuses requests. Against a Machine Fly reports `stopped`/`suspended`/
|
|
51
|
+
* `destroyed` the same body is the REAL precondition — the Machine is not
|
|
52
|
+
* running, and {@link FlyioSandboxProvider.startMachine} is what answers it;
|
|
53
|
+
* repeating the exec would only burn the caller's budget.
|
|
46
54
|
*
|
|
47
55
|
* @param error - The failure an exec attempt raised.
|
|
56
|
+
* @param machineState - The state `GET /machines/{id}` reports for the Machine,
|
|
57
|
+
* or `null`/`undefined` when it could not be read (which classifies case 2 as
|
|
58
|
+
* NOT transient — an unknown state is never assumed recoverable).
|
|
48
59
|
* @returns `true` when repeating the exec is the right recovery.
|
|
49
60
|
*/
|
|
50
|
-
export function isTransientExecPrecondition(error) {
|
|
61
|
+
export function isTransientExecPrecondition(error, machineState) {
|
|
51
62
|
if (!(error instanceof FlyApiError) || error.status !== 412)
|
|
52
63
|
return false;
|
|
53
|
-
|
|
64
|
+
if (EXEC_BODY_DROPPED_CONNECTION.test(error.body))
|
|
65
|
+
return true;
|
|
66
|
+
return EXEC_BODY_MACHINE_NOT_RUNNING.test(error.body) && isMachineWarmingUp(machineState);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether a state Fly reports for a Machine means the Machine is up, or on its
|
|
70
|
+
* way up — the states in which an exec that was refused for `machine not
|
|
71
|
+
* running` is the exec endpoint lagging the state, not the truth.
|
|
72
|
+
*
|
|
73
|
+
* A deliberate allow-list rather than "anything but stopped/suspended": the
|
|
74
|
+
* three states here are the ones a refused exec was observed to recover from,
|
|
75
|
+
* and every other state — `stopped`, `suspended`, `destroyed`, `failed`,
|
|
76
|
+
* `created`, unreadable — keeps today's immediate throw. Widening it without
|
|
77
|
+
* that evidence would turn a real precondition into a retry budget spent
|
|
78
|
+
* against a Machine that is not coming up.
|
|
79
|
+
*
|
|
80
|
+
* @param state - The Machine state from `GET /machines/{id}`, if readable.
|
|
81
|
+
* @returns `true` when waiting and repeating the exec can clear the refusal.
|
|
82
|
+
*/
|
|
83
|
+
export function isMachineWarmingUp(state) {
|
|
84
|
+
return state === 'started' || state === 'starting' || state === 'replacing';
|
|
54
85
|
}
|
|
55
86
|
/**
|
|
56
87
|
* Default sandbox image. Fly pulls images itself, so this MUST be a reference
|
|
@@ -184,21 +215,25 @@ const MACHINE_CREATE_RETRY_STATUSES = [404, 412];
|
|
|
184
215
|
* Machine's exec agent died before the command ran: `412 {"error":
|
|
185
216
|
* "failed_precondition: exec request failed: EOF"}`. The Machine is `started`;
|
|
186
217
|
* the agent inside it dropped the request (a Machine seconds past boot, or
|
|
187
|
-
* Fly's own exec burst).
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
*
|
|
218
|
+
* Fly's own exec burst).
|
|
219
|
+
*/
|
|
220
|
+
const EXEC_BODY_DROPPED_CONNECTION = /exec request failed/i;
|
|
221
|
+
/**
|
|
222
|
+
* The OTHER 412 the exec endpoint answers — `{"error":"failed_precondition:
|
|
223
|
+
* machine not running"}`. Whether that is the real precondition or the exec
|
|
224
|
+
* endpoint lagging a Machine that is up cannot be read off the body, so it is
|
|
225
|
+
* settled by the state Fly reports for the Machine at that moment
|
|
226
|
+
* ({@link isMachineWarmingUp}).
|
|
192
227
|
*/
|
|
193
|
-
const EXEC_BODY_TRANSIENT = /exec request failed/i;
|
|
194
|
-
/** The real-precondition 412, which retrying an exec cannot fix. */
|
|
195
228
|
const EXEC_BODY_MACHINE_NOT_RUNNING = /machine not running/i;
|
|
196
229
|
/**
|
|
197
|
-
* Attempts for one exec when Fly answers
|
|
198
|
-
* {@link
|
|
199
|
-
* under 12 s of waiting, against the ~75 s each fresh production
|
|
200
|
-
* when this is left to a caller: X1 run flash-m85 (2026-10-16)
|
|
201
|
-
* consecutive deploys, and the run, to
|
|
230
|
+
* Attempts for one exec when Fly answers a transient 412 — either body of
|
|
231
|
+
* {@link isTransientExecPrecondition}. Four attempts over the three backoffs
|
|
232
|
+
* below is under 12 s of waiting, against the ~75 s each fresh production
|
|
233
|
+
* deploy costs when this is left to a caller: X1 run flash-m85 (2026-10-16)
|
|
234
|
+
* lost three consecutive deploys, and the run, to the dropped connection;
|
|
235
|
+
* flash-m94 (2026-10-16) lost 65 s and a whole redeploy to the same budget not
|
|
236
|
+
* covering `machine not running` on a Machine Fly reported `started`.
|
|
202
237
|
*/
|
|
203
238
|
const EXEC_PRECONDITION_ATTEMPTS = 4;
|
|
204
239
|
/**
|
|
@@ -1837,6 +1872,34 @@ class FlyioSandboxProvider {
|
|
|
1837
1872
|
// ---------------------------------------------------------------------------
|
|
1838
1873
|
// Sandbox
|
|
1839
1874
|
// ---------------------------------------------------------------------------
|
|
1875
|
+
/**
|
|
1876
|
+
* Reads the state Fly reports for a Machine, or `null` when it cannot be read.
|
|
1877
|
+
*
|
|
1878
|
+
* Best-effort on purpose: this runs both on the failure path and just before a
|
|
1879
|
+
* retry, and a failed diagnostic read must not replace the failure it is
|
|
1880
|
+
* describing. `null` is meaningful — it never classifies a refusal as
|
|
1881
|
+
* recoverable (see {@link isTransientExecPrecondition}).
|
|
1882
|
+
*
|
|
1883
|
+
* @param app - The Fly app name.
|
|
1884
|
+
* @param machineId - The Fly Machine id.
|
|
1885
|
+
* @returns The Machine's state, or `null` when the read failed or 404'd.
|
|
1886
|
+
*/
|
|
1887
|
+
async readMachineState(app, machineId) {
|
|
1888
|
+
try {
|
|
1889
|
+
const machine = await this.client.request(`/apps/${app}/machines/${machineId}`, {
|
|
1890
|
+
nullOn: [404],
|
|
1891
|
+
});
|
|
1892
|
+
return machine?.state ?? null;
|
|
1893
|
+
}
|
|
1894
|
+
catch (error) {
|
|
1895
|
+
logger.debug('Could not read the Machine state while an exec was refused', {
|
|
1896
|
+
app,
|
|
1897
|
+
machineId,
|
|
1898
|
+
error,
|
|
1899
|
+
});
|
|
1900
|
+
return null;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1840
1903
|
/**
|
|
1841
1904
|
* Issues one Fly exec call against a Machine.
|
|
1842
1905
|
*
|
|
@@ -1875,28 +1938,38 @@ class FlyioSandboxProvider {
|
|
|
1875
1938
|
return response ?? {};
|
|
1876
1939
|
}
|
|
1877
1940
|
catch (error) {
|
|
1878
|
-
|
|
1879
|
-
|
|
1941
|
+
// Which 412 this is cannot be read off the body alone: `machine not
|
|
1942
|
+
// running` is transient against a Machine Fly reports `started` and the
|
|
1943
|
+
// real precondition against one it reports `stopped`. So the state is
|
|
1944
|
+
// read here, once, and the same read decides the classification, the
|
|
1945
|
+
// wait below, and the state the error finally names.
|
|
1946
|
+
const state = error instanceof FlyApiError && error.status === 412
|
|
1947
|
+
? await this.readMachineState(app, machineId)
|
|
1948
|
+
: null;
|
|
1949
|
+
if (!isTransientExecPrecondition(error, state)) {
|
|
1950
|
+
throw await this.withMachineState(error, app, machineId, state);
|
|
1880
1951
|
}
|
|
1881
1952
|
if (attempt >= EXEC_PRECONDITION_ATTEMPTS || Date.now() >= deadline) {
|
|
1882
|
-
logger.warn('Fly kept
|
|
1953
|
+
logger.warn('Fly kept refusing the exec with a transient precondition', {
|
|
1883
1954
|
app,
|
|
1884
1955
|
machineId,
|
|
1885
1956
|
attempt,
|
|
1957
|
+
state: state ?? 'unreadable',
|
|
1886
1958
|
});
|
|
1887
|
-
throw await this.withMachineState(error, app, machineId);
|
|
1959
|
+
throw await this.withMachineState(error, app, machineId, state);
|
|
1888
1960
|
}
|
|
1889
|
-
logger.warn('Fly
|
|
1961
|
+
logger.warn('Fly refused the exec with a transient precondition; retrying', {
|
|
1890
1962
|
app,
|
|
1891
1963
|
machineId,
|
|
1892
1964
|
attempt,
|
|
1893
1965
|
attempts: EXEC_PRECONDITION_ATTEMPTS,
|
|
1966
|
+
state: state ?? 'unreadable',
|
|
1894
1967
|
});
|
|
1895
1968
|
// The Machine is up (that is what makes this 412 the transient one), but
|
|
1896
1969
|
// re-read it anyway: `stop`/`suspend` can land between attempts, and
|
|
1897
1970
|
// retrying an exec against a Machine that is not running would repeat a
|
|
1898
1971
|
// 412 that waiting — not repeating — is what answers.
|
|
1899
|
-
await this.waitForExecReady(app, machineId);
|
|
1972
|
+
await this.waitForExecReady(app, machineId, state);
|
|
1900
1973
|
await this.client.sleep(execPreconditionBackoffMs(attempt));
|
|
1901
1974
|
}
|
|
1902
1975
|
}
|
|
@@ -1908,17 +1981,17 @@ class FlyioSandboxProvider {
|
|
|
1908
1981
|
*
|
|
1909
1982
|
* A wait that does not reach `started` is logged and the exec is retried
|
|
1910
1983
|
* anyway: the retry is the recovery this path exists for, and turning a
|
|
1911
|
-
* recoverable
|
|
1912
|
-
*
|
|
1984
|
+
* recoverable refusal into a start-timeout error would hide the condition the
|
|
1985
|
+
* caller's error message is supposed to name.
|
|
1913
1986
|
*
|
|
1914
1987
|
* @param app - The Fly app name.
|
|
1915
1988
|
* @param machineId - The Fly Machine id.
|
|
1989
|
+
* @param state - The state already read for this Machine, when the caller read
|
|
1990
|
+
* one (the exec retry does); read again when omitted.
|
|
1916
1991
|
*/
|
|
1917
|
-
async waitForExecReady(app, machineId) {
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
.catch(() => null);
|
|
1921
|
-
if (machine?.state === 'started')
|
|
1992
|
+
async waitForExecReady(app, machineId, state) {
|
|
1993
|
+
const machineState = state === undefined ? await this.readMachineState(app, machineId) : state;
|
|
1994
|
+
if (machineState === 'started')
|
|
1922
1995
|
return;
|
|
1923
1996
|
try {
|
|
1924
1997
|
await this.waitForStarted(app, machineId);
|
|
@@ -1927,7 +2000,7 @@ class FlyioSandboxProvider {
|
|
|
1927
2000
|
logger.warn('The Machine did not report started after a refused exec; retrying the exec', {
|
|
1928
2001
|
app,
|
|
1929
2002
|
machineId,
|
|
1930
|
-
state:
|
|
2003
|
+
state: machineState ?? 'unknown',
|
|
1931
2004
|
error,
|
|
1932
2005
|
});
|
|
1933
2006
|
}
|
|
@@ -1944,19 +2017,19 @@ class FlyioSandboxProvider {
|
|
|
1944
2017
|
* @param error - The failure to annotate.
|
|
1945
2018
|
* @param app - The Fly app name.
|
|
1946
2019
|
* @param machineId - The Fly Machine id.
|
|
2020
|
+
* @param knownState - The state already read for this Machine, when the caller
|
|
2021
|
+
* read one (the exec retry does); read again when omitted.
|
|
1947
2022
|
* @returns The original error, or a 412 carrying the Machine's state.
|
|
1948
2023
|
*/
|
|
1949
|
-
async withMachineState(error, app, machineId) {
|
|
2024
|
+
async withMachineState(error, app, machineId, knownState) {
|
|
1950
2025
|
if (!(error instanceof Error))
|
|
1951
2026
|
return new Error(String(error));
|
|
1952
2027
|
if (!(error instanceof FlyApiError) || error.status !== 412)
|
|
1953
2028
|
return error;
|
|
1954
2029
|
if (/machine state:/i.test(error.message))
|
|
1955
2030
|
return error;
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
.catch(() => null);
|
|
1959
|
-
const state = machine?.state ?? 'unreadable';
|
|
2031
|
+
const read = knownState === undefined ? await this.readMachineState(app, machineId) : knownState;
|
|
2032
|
+
const state = read ?? 'unreadable';
|
|
1960
2033
|
return new FlyApiError(error.method, error.path, error.status, `${error.body} (machine ${machineId} state: ${state})`, { cause: error });
|
|
1961
2034
|
}
|
|
1962
2035
|
/**
|