Skip to content

Commit 09ff9c0

Browse files
committed
fix(cli): keep a sub-millisecond timeout bounded
Zero is how this function says "no bound", so rounding a positive SIM_TIMEOUT_SECONDS down to zero inverted the request: anything under 0.0005s asked for the shortest possible timeout and got none at all, leaving a stalled request to hang. Introduced by the rounding that fixed the fractional-millisecond rejection. Floored at 1ms for every positive value; only a literal 0 still disables.
1 parent 6f2fdc4 commit 09ff9c0

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

packages/sim-cli/src/http/client.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,22 @@ describe('a request that never answers', () => {
378378
await expect(client().request('/api/v2/workflows')).resolves.toBeDefined()
379379
})
380380

381+
it('keeps a bound below half a millisecond bounded, rather than disabling it', async () => {
382+
// Zero means "no bound", so rounding a positive value down to zero inverted
383+
// the request: the shortest timeout anyone could ask for became none.
384+
vi.stubEnv('SIM_TIMEOUT_SECONDS', '0.0004')
385+
const fetchMock = vi.fn().mockResolvedValue(
386+
new Response(JSON.stringify({ data: [] }), {
387+
status: 200,
388+
headers: { 'content-type': 'application/json' },
389+
})
390+
)
391+
vi.stubGlobal('fetch', fetchMock)
392+
393+
await client().request('/api/v2/workflows')
394+
expect(fetchMock.mock.calls[0][1].signal).toBeInstanceOf(AbortSignal)
395+
})
396+
381397
it('refuses a delay longer than Node can wait, which would silently become 1ms', async () => {
382398
// Past 2^31-1 ms Node does not fail — it clamps to 1ms, so the request the
383399
// caller asked to wait longest for would be the first one aborted.

packages/sim-cli/src/http/client.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,10 @@ function resolveTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {
300300

301301
// Rounded because a fractional millisecond is rejected outright by
302302
// `AbortSignal.timeout`, and a sub-millisecond timeout is not a distinction
303-
// anyone is drawing.
304-
const ms = Math.round(seconds * 1000)
303+
// anyone is drawing. Floored at 1ms for anything above zero: rounding alone
304+
// sent a bound under 0.0005s to 0, which this function reserves for "no
305+
// bound at all", so asking for the shortest possible timeout produced none.
306+
const ms = seconds === 0 ? 0 : Math.max(1, Math.round(seconds * 1000))
305307
if (ms > MAX_TIMEOUT_MS) {
306308
throw new SimApiError(
307309
`SIM_TIMEOUT_SECONDS "${raw}" is longer than Node can wait (${Math.floor(MAX_TIMEOUT_MS / 1000)}s). Use 0 to wait indefinitely.`,

0 commit comments

Comments
 (0)