Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/lib/agent-onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
import fs from "fs";
import os from "os";
import path from "path";
import { spawnSync } from "child_process";

import { ROOT, run } from "./runner";
import { loadAgent, resolveAgentName, type AgentDefinition } from "./agent-defs";
import { getProviderSelectionConfig } from "./inference-config";
import * as onboardSession from "./onboard-session";
import { sleepSeconds } from "./wait";

export interface OnboardContext {
step: (current: number, total: number, message: string) => void;
Expand Down Expand Up @@ -102,7 +102,7 @@ export function getAgentPermissivePolicyPath(agent: AgentDefinition): string | n
}

function sleep(seconds: number): void {
spawnSync("sleep", [String(seconds)]);
sleepSeconds(seconds);
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/lib/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { sleepSeconds } from "./wait";

export interface DeployCredentials {
NVIDIA_API_KEY?: string | null;
OPENAI_API_KEY?: string | null;
Expand Down Expand Up @@ -333,7 +335,7 @@ export async function executeDeploy(opts: DeployExecutionOptions): Promise<void>
return fail([` Timed out waiting for Brev instance readiness for ${name}`], error, exit);
}
stdoutWrite(".");
spawnSync("sleep", ["3"]);
sleepSeconds(3);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// ── SSH trust-on-first-use (TOFU) ──────────────────────────────
Expand Down Expand Up @@ -371,7 +373,7 @@ export async function executeDeploy(opts: DeployExecutionOptions): Promise<void>
);
}
stdoutWrite(".");
spawnSync("sleep", ["3"]);
sleepSeconds(3);
}

const sshOpts = buildSshOpts(knownHostsFile, shellQuote);
Expand Down
5 changes: 3 additions & 2 deletions src/lib/nim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { run, runCapture } = require("./runner");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { sleepSeconds } = require("./wait");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const nimImages = require("../../bin/lib/nim-images.json");

import { VLLM_PORT } from "./ports";
Expand Down Expand Up @@ -280,8 +282,7 @@ export function waitForNimHealth(port = VLLM_PORT, timeout = 300): boolean {
} catch {
/* ignored */
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
require("child_process").spawnSync("sleep", [String(intervalSec)]);
sleepSeconds(intervalSec);
}
console.error(` NIM did not become healthy within ${timeout}s.`);
return false;
Expand Down
5 changes: 4 additions & 1 deletion src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ const {
// Shared constant so getSuggestedPolicyPresets() and setupPoliciesWithSelection()
// stay in sync.
const LOCAL_INFERENCE_PROVIDERS = ["ollama-local", "vllm-local"];
const {
sleepSeconds,
} = require("./wait");
Comment thread
ksapru marked this conversation as resolved.
const { inferContainerRuntime, isWsl, shouldPatchCoredns } = require("./platform");
const { resolveOpenshell } = require("./resolve-openshell");
const {
Expand Down Expand Up @@ -2214,7 +2217,7 @@ function installOpenshell() {
}

function sleep(seconds) {
require("child_process").spawnSync("sleep", [String(seconds)]);
sleepSeconds(seconds);
}

function destroyGateway() {
Expand Down
23 changes: 23 additions & 0 deletions src/lib/wait.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

/**
* Synchronous waiting primitives for CLI commands.
*/

/**
* Synchronously sleep for the given number of milliseconds.
* Uses Atomics.wait to block without pegging the CPU.
*/
export function sleepMs(ms: number): void {
if (ms <= 0 || !Number.isFinite(ms)) return;
const buffer = new Int32Array(new SharedArrayBuffer(4));
Atomics.wait(buffer, 0, 0, ms);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Synchronously sleep for the given number of seconds.
*/
export function sleepSeconds(seconds: number): void {
sleepMs(seconds * 1000);
}
3 changes: 2 additions & 1 deletion src/nemoclaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const sandboxVersion = require("./lib/sandbox-version");
const sandboxState = require("./lib/sandbox-state");
const { ensureOllamaAuthProxy } = require("./lib/onboard");
const skillInstall = require("./lib/skill-install");
const { sleepSeconds } = require("./lib/wait");
const { parseSandboxPhase } = require("./lib/gateway-state");
const {
getActiveSandboxSessions,
Expand Down Expand Up @@ -332,7 +333,7 @@ function checkAndRecoverSandboxProcesses(sandboxName, { quiet = false } = {}) {
const recovered = recoverSandboxProcesses(sandboxName);
if (recovered) {
// Wait for gateway to bind its HTTP port before declaring success
spawnSync("sleep", ["3"]);
sleepSeconds(3);
if (isSandboxGatewayRunning(sandboxName) !== true) {
// Gateway process started but HTTP endpoint never came up
if (!quiet) {
Expand Down
41 changes: 41 additions & 0 deletions test/wait.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import assert from "node:assert";
import { describe, expect, it } from "vitest";
import { sleepMs, sleepSeconds } from "../src/lib/wait.js";

describe("wait utility", () => {
it("sleepMs blocks for approximately the requested time", () => {
const start = performance.now();
sleepMs(100);
const end = performance.now();
const duration = end - start;

// Allow for some jitter, but should be at least 100ms.
// Increased upper bound to 500ms to avoid CI flakes on loaded runners.
assert.ok(duration >= 100, `duration ${duration}ms < 100ms`);
assert.ok(duration < 500, `duration ${duration}ms > 500ms`);
});

it("sleepSeconds blocks for approximately the requested time", () => {
const start = performance.now();
sleepSeconds(0.1);
const end = performance.now();
const duration = end - start;

assert.ok(duration >= 100, `duration ${duration}ms < 100ms`);
assert.ok(duration < 500, `duration ${duration}ms > 500ms`);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("returns immediately for zero, negative, or non-finite time", () => {
const start = performance.now();
sleepMs(0);
sleepMs(-50);
sleepMs(NaN);
sleepMs(Infinity);
const end = performance.now();
const duration = end - start;
assert.ok(duration < 50, `duration ${duration}ms > 50ms`);
});
});
Loading