fix(desktop): exe identity stamping stops retrying when the rcedit binary itself is missing
apps/desktop/scripts/set-exe-identity.mjs::stampExeIdentity retried every rcedit
rejection with 0.5/1/2 s backoff. A failure to SPAWN rcedit (bin/rcedit-x64.exe
missing or not executable) is permanent, so the only effect was a 3.5 s delay
before after-pack.mjs's warning.
The npm rcedit wrapper (@malept/cross-spawn-promise) reports a spawn failure as
CrossSpawnError with the errno on `originalError.code`; a non-zero rcedit exit
("Unable to commit changes") is an ExitCodeError with a numeric `code`. Skip the
retry loop when `originalError.code ?? code` is ENOENT/EACCES and rethrow as-is.
Probe: stub rcedit rejecting with that shape -> before 4 attempts, sleeps
[500,1000,2000]; after 1 attempt, no sleeps. Transient-lock retry unchanged.
Part of #112544 (optional review atom).
This commit is contained in:
@@ -44,12 +44,20 @@ import { isMain } from './utils.mjs'
|
||||
|
||||
// A real-time file scanner (AV/EDR) holds a short exclusive handle on a freshly
|
||||
// written exe; rcedit's resource commit then fails with "Unable to commit
|
||||
// changes" and succeeds seconds later on identical input. Retrying on ANY
|
||||
// rcedit failure keeps the shape simple — a permanent failure costs 3.5 s more
|
||||
// before after-pack.mjs swallows it. Delays sized to the field report: the
|
||||
// lock was still held 5 s after a first attempt in some runs.
|
||||
// changes" and succeeds seconds later on identical input. Delays sized to the
|
||||
// field report: the lock was still held 5 s after a first attempt in some runs.
|
||||
const RCEDIT_COMMIT_RETRY_DELAYS_MS = [500, 1000, 2000]
|
||||
|
||||
// A failure to spawn the rcedit binary itself (missing or not executable) is
|
||||
// permanent; waiting 3.5 s on it only delays after-pack.mjs's warning. The npm
|
||||
// rcedit wrapper surfaces the spawn error as `originalError` on its rejection,
|
||||
// while a non-zero rcedit exit carries a numeric `code`.
|
||||
const RCEDIT_PERMANENT_SPAWN_CODES = new Set(['ENOENT', 'EACCES'])
|
||||
|
||||
function isPermanentRceditFailure(err) {
|
||||
return RCEDIT_PERMANENT_SPAWN_CODES.has(err?.originalError?.code ?? err?.code)
|
||||
}
|
||||
|
||||
function wait(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
@@ -91,7 +99,7 @@ async function stampExeIdentity(
|
||||
break
|
||||
} catch (err) {
|
||||
const delay = RCEDIT_COMMIT_RETRY_DELAYS_MS[attempt]
|
||||
if (delay === undefined) {
|
||||
if (delay === undefined || isPermanentRceditFailure(err)) {
|
||||
throw err
|
||||
}
|
||||
console.warn(`[set-exe-identity] rcedit failed; retrying in ${delay}ms (${err.message})`)
|
||||
|
||||
@@ -56,3 +56,32 @@ test('stops retrying after the bounded rcedit retry budget is exhausted', async
|
||||
fs.rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('does not retry when the rcedit binary itself cannot be spawned (ENOENT/EACCES)', async () => {
|
||||
// #112544 follow-up: the npm rcedit wrapper reports a spawn failure via
|
||||
// `originalError`; that toolchain breakage is permanent, so the 3.5 s
|
||||
// transient-lock retry budget must not be spent on it.
|
||||
const { exe, root } = makeDesktopRoot()
|
||||
let attempts = 0
|
||||
const delays = []
|
||||
const spawnFailure = Object.assign(new Error('Error executing command (rcedit-x64.exe):\nspawn rcedit-x64.exe ENOENT'), {
|
||||
originalError: Object.assign(new Error('spawn rcedit-x64.exe ENOENT'), { code: 'ENOENT' })
|
||||
})
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
stampExeIdentity(exe, root, {
|
||||
rcedit: async () => {
|
||||
attempts += 1
|
||||
throw spawnFailure
|
||||
},
|
||||
sleep: async delay => delays.push(delay)
|
||||
}),
|
||||
/ENOENT/
|
||||
)
|
||||
assert.equal(attempts, 1)
|
||||
assert.deepEqual(delays, [])
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user