mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(desktop): run venv-blocker scan async off the main-process event loop
The preflight's execFileSync froze the Electron UI event loop for up to 15s while psutil scanned the full process table. Convert scanVenvBlockers to async execFile and await it in applyUpdates; tests updated to async DI stubs. Also adds trailing newlines.
This commit is contained in:
parent
d210500b54
commit
1dd1c449bb
4 changed files with 71 additions and 42 deletions
|
|
@ -2891,7 +2891,7 @@ async function applyUpdates(opts = {}) {
|
|||
// malformed output, missing psutil) abort the handoff — never proceed
|
||||
// to the detached updater when the venv state is unknown.
|
||||
if (IS_WINDOWS) {
|
||||
const scanOutcome = scanVenvBlockers(updateRoot)
|
||||
const scanOutcome = await scanVenvBlockers(updateRoot)
|
||||
|
||||
if (scanOutcome.kind === 'blocked') {
|
||||
const message = formatBlockerMessage(scanOutcome.result)
|
||||
|
|
|
|||
|
|
@ -165,46 +165,46 @@ describe('scanVenvBlockers', () => {
|
|||
})
|
||||
|
||||
function execReturn(json: string): any {
|
||||
return ((...args: any[]) => json) as any
|
||||
return (async (...args: any[]) => ({ stdout: json, stderr: '' })) as any
|
||||
}
|
||||
|
||||
function execThrow(status: number, stderr: string): any {
|
||||
return ((...args: any[]) => { const e: any = new Error(); e.status = status; e.stderr = Buffer.from(stderr); throw e }) as any
|
||||
return (async (...args: any[]) => { const e: any = new Error(); e.status = status; e.stderr = Buffer.from(stderr); throw e }) as any
|
||||
}
|
||||
|
||||
it('clear scan returns clear', () => {
|
||||
assert.equal(scanVenvBlockers('/r', execReturn(okJson), stubVenv).kind, 'clear')
|
||||
it('clear scan returns clear', async () => {
|
||||
assert.equal((await scanVenvBlockers('/r', execReturn(okJson), stubVenv)).kind, 'clear')
|
||||
})
|
||||
|
||||
it('blocked scan returns blocked', () => {
|
||||
assert.equal(scanVenvBlockers('/r', execReturn(blockedJson), stubVenv).kind, 'blocked')
|
||||
it('blocked scan returns blocked', async () => {
|
||||
assert.equal((await scanVenvBlockers('/r', execReturn(blockedJson), stubVenv)).kind, 'blocked')
|
||||
})
|
||||
|
||||
it('non-zero exit is probe-failure', () => {
|
||||
const o = scanVenvBlockers('/r', execThrow(2, 'ModuleNotFoundError'), stubVenv)
|
||||
it('non-zero exit is probe-failure', async () => {
|
||||
const o = await scanVenvBlockers('/r', execThrow(2, 'ModuleNotFoundError'), stubVenv)
|
||||
assert.equal(o.kind, 'probe-failure')
|
||||
})
|
||||
|
||||
it('missing venv python is probe-failure', () => {
|
||||
const o = scanVenvBlockers('/r', execReturn(okJson), () => null)
|
||||
it('missing venv python is probe-failure', async () => {
|
||||
const o = await scanVenvBlockers('/r', execReturn(okJson), () => null)
|
||||
assert.equal(o.kind, 'probe-failure')
|
||||
})
|
||||
|
||||
it('malformed subprocess output is probe-failure', () => {
|
||||
const o = scanVenvBlockers('/r', execReturn('bad json'), stubVenv)
|
||||
it('malformed subprocess output is probe-failure', async () => {
|
||||
const o = await scanVenvBlockers('/r', execReturn('bad json'), stubVenv)
|
||||
assert.equal(o.kind, 'probe-failure')
|
||||
})
|
||||
|
||||
it('calls subprocess with correct args, cwd, timeout, stdio', () => {
|
||||
it('calls subprocess with correct args, cwd and timeout', async () => {
|
||||
const calls: any[] = []
|
||||
|
||||
const spy = ((cmd: string, args: string[], opts: any) => {
|
||||
calls.push({ cmd, args, cwd: opts.cwd, timeout: opts.timeout, stdio: opts.stdio })
|
||||
const spy = (async (cmd: string, args: string[], opts: any) => {
|
||||
calls.push({ cmd, args, cwd: opts.cwd, timeout: opts.timeout })
|
||||
|
||||
return okJson
|
||||
return { stdout: okJson, stderr: '' }
|
||||
}) as any
|
||||
|
||||
scanVenvBlockers('/update/root', spy, stubVenv)
|
||||
await scanVenvBlockers('/update/root', spy, stubVenv)
|
||||
assert.equal(calls.length, 1)
|
||||
const c = calls[0]
|
||||
assert.ok(c.cmd.endsWith('python.exe'))
|
||||
|
|
@ -212,6 +212,5 @@ describe('scanVenvBlockers', () => {
|
|||
assert.equal(c.cwd, '/update/root')
|
||||
assert.equal(typeof c.timeout, 'number')
|
||||
assert.ok(c.timeout > 0)
|
||||
assert.deepEqual(c.stdio, ['ignore', 'pipe', 'pipe'])
|
||||
})
|
||||
})
|
||||
|
|
@ -7,9 +7,12 @@
|
|||
* returns a typed result for the Desktop update preflight.
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { execFile } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
|
|
@ -106,15 +109,17 @@ export function parseVenvBlockerScanOutput(raw: string): ScanOutcome {
|
|||
}
|
||||
|
||||
/**
|
||||
* Run the venv-blocker scan subprocess. Accepts optional overrides for
|
||||
* testing (dependency injection).
|
||||
* Run the venv-blocker scan subprocess. Async so the Electron main-process
|
||||
* event loop is never blocked by the psutil process scan (up to 15s on a
|
||||
* loaded Windows box). Accepts optional overrides for testing (dependency
|
||||
* injection).
|
||||
*/
|
||||
export function scanVenvBlockers(
|
||||
export async function scanVenvBlockers(
|
||||
updateRoot: string,
|
||||
execOverride?: typeof execFileSync,
|
||||
execOverride?: typeof execFileAsync,
|
||||
resolveOverride?: typeof resolveVenvPython,
|
||||
): ScanOutcome {
|
||||
const execFn = execOverride || execFileSync
|
||||
): Promise<ScanOutcome> {
|
||||
const execFn = execOverride || execFileAsync
|
||||
const resolveFn = resolveOverride || resolveVenvPython
|
||||
const venvPython = resolveFn(updateRoot)
|
||||
|
||||
|
|
@ -125,21 +130,20 @@ export function scanVenvBlockers(
|
|||
let stdout: string
|
||||
|
||||
try {
|
||||
const proc = execFn(
|
||||
const proc = await execFn(
|
||||
venvPython,
|
||||
['-m', SCAN_MODULE],
|
||||
{
|
||||
cwd: updateRoot,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout: SCAN_TIMEOUT_MS,
|
||||
windowsHide: true,
|
||||
} as any,
|
||||
)
|
||||
|
||||
stdout = (proc as unknown as string)
|
||||
stdout = String((proc as any).stdout ?? '')
|
||||
} catch (err: any) {
|
||||
const diag = [`exit code ${err.status ?? -1}`]
|
||||
const diag = [`exit code ${err.status ?? err.code ?? -1}`]
|
||||
|
||||
if (err.stderr) {diag.push(String(err.stderr).slice(0, 200))}
|
||||
|
||||
|
|
|
|||
50
package-lock.json
generated
50
package-lock.json
generated
|
|
@ -165,6 +165,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^8.59.1",
|
||||
"@typescript-eslint/parser": "^8.59.1",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"bippy": "0.5.43",
|
||||
"concurrently": "^10.0.3",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "40.10.2",
|
||||
|
|
@ -183,8 +184,7 @@
|
|||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.10",
|
||||
"vitest": "^4.1.5",
|
||||
"wait-on": "^9.0.5",
|
||||
"bippy": "0.5.43"
|
||||
"wait-on": "^9.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
|
|
@ -1939,6 +1939,7 @@
|
|||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -1955,6 +1956,7 @@
|
|||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -1971,6 +1973,7 @@
|
|||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -1987,6 +1990,7 @@
|
|||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2003,6 +2007,7 @@
|
|||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2019,6 +2024,7 @@
|
|||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2035,6 +2041,7 @@
|
|||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2051,6 +2058,7 @@
|
|||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2067,6 +2075,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2083,6 +2092,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2099,6 +2109,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2115,6 +2126,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2131,6 +2143,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2147,6 +2160,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2163,6 +2177,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2179,6 +2194,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2195,6 +2211,7 @@
|
|||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2211,6 +2228,7 @@
|
|||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2227,6 +2245,7 @@
|
|||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2243,6 +2262,7 @@
|
|||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2259,6 +2279,7 @@
|
|||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2275,6 +2296,7 @@
|
|||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2291,6 +2313,7 @@
|
|||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2307,6 +2330,7 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2323,6 +2347,7 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -2339,6 +2364,7 @@
|
|||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -7730,6 +7756,16 @@
|
|||
"integrity": "sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bippy": {
|
||||
"version": "0.5.43",
|
||||
"resolved": "https://registry.npmjs.org/bippy/-/bippy-0.5.43.tgz",
|
||||
"integrity": "sha512-Tvu7b1M7+d8b9/YHaCeODEsi2CgbuoBql+dWSBrNnCuqJ1gMUeY3i0r+319hvjjl5GVBP6FFWxrKnq3fhZER0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
|
|
@ -19748,16 +19784,6 @@
|
|||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bippy": {
|
||||
"version": "0.5.43",
|
||||
"resolved": "https://registry.npmjs.org/bippy/-/bippy-0.5.43.tgz",
|
||||
"integrity": "sha512-Tvu7b1M7+d8b9/YHaCeODEsi2CgbuoBql+dWSBrNnCuqJ1gMUeY3i0r+319hvjjl5GVBP6FFWxrKnq3fhZER0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue