refactor: deduplicate sleep helpers into src/utils (#399)

This commit is contained in:
Cameron
2026-02-25 14:29:31 -08:00
committed by GitHub
parent 07d6939ead
commit 3c7352274e
4 changed files with 46 additions and 23 deletions

View File

@@ -20,6 +20,7 @@ import { randomUUID } from 'node:crypto';
import { dirname, resolve } from 'node:path';
import type { AgentStore, LastMessageTarget } from './types.js';
import { getDataDir } from '../utils/paths.js';
import { sleepSync } from '../utils/time.js';
import { createLogger } from '../logger.js';
const log = createLogger('Store');
@@ -28,7 +29,6 @@ const DEFAULT_STORE_PATH = 'lettabot-agent.json';
const LOCK_RETRY_MS = 25;
const LOCK_TIMEOUT_MS = 5_000;
const LOCK_STALE_MS = 30_000;
const SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
interface StoreV2 {
version: 2;
@@ -40,23 +40,6 @@ interface ParsedStore {
wasV1: boolean;
}
let warnedAboutBusyWait = false;
function sleepSync(ms: number): void {
if (typeof Atomics.wait === 'function') {
Atomics.wait(SLEEP_BUFFER, 0, 0, ms);
return;
}
if (!warnedAboutBusyWait) {
log.warn('Atomics.wait unavailable, falling back to busy-wait for lock retries');
warnedAboutBusyWait = true;
}
const end = Date.now() + ms;
while (Date.now() < end) {
// Busy-wait fallback -- should not be reached in standard Node.js (v8+)
}
}
export class Store {
private readonly storePath: string;
private readonly lockPath: string;
@@ -197,7 +180,9 @@ export class Store {
if (Date.now() - start >= LOCK_TIMEOUT_MS) {
throw new Error(`Timed out waiting for store lock: ${this.lockPath}`);
}
sleepSync(LOCK_RETRY_MS);
sleepSync(LOCK_RETRY_MS, () => {
log.warn('Atomics.wait unavailable, falling back to busy-wait for lock retries');
});
}
}
}

View File

@@ -25,6 +25,7 @@ import {
import { isLettaApiUrl } from './utils/server.js';
import { getDataDir, getWorkingDir, hasRailwayVolume } from './utils/paths.js';
import { parseCsvList, parseNonNegativeNumber } from './utils/parse.js';
import { sleep } from './utils/time.js';
import { createLogger, setLogLevel } from './logger.js';
const log = createLogger('Config');
@@ -211,10 +212,6 @@ const DISCOVERY_LOCK_TIMEOUT_MS = 15_000;
const DISCOVERY_LOCK_STALE_MS = 60_000;
const DISCOVERY_LOCK_RETRY_MS = 100;
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getDiscoveryLockPath(agentName: string): string {
const safe = agentName
.trim()

16
src/utils/time.test.ts Normal file
View File

@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { sleep, sleepSync } from './time.js';
describe('sleep', () => {
it('waits asynchronously', async () => {
const start = Date.now();
await sleep(10);
expect(Date.now() - start).toBeGreaterThanOrEqual(8);
});
});
describe('sleepSync', () => {
it('does not throw for zero delay', () => {
expect(() => sleepSync(0)).not.toThrow();
});
});

25
src/utils/time.ts Normal file
View File

@@ -0,0 +1,25 @@
/**
* Shared timing helpers used across startup and persistence paths.
*/
const SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
let warnedAboutBusyWait = false;
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function sleepSync(ms: number, onBusyWait?: () => void): void {
if (typeof Atomics.wait === 'function') {
Atomics.wait(SLEEP_BUFFER, 0, 0, ms);
return;
}
if (!warnedAboutBusyWait) {
onBusyWait?.();
warnedAboutBusyWait = true;
}
const end = Date.now() + ms;
while (Date.now() < end) {
// Busy-wait fallback -- should not be reached in standard Node.js (v8+)
}
}