diff --git a/src/rizin/RizinDebugSessionManager.ts b/src/rizin/RizinDebugSessionManager.ts index c3fa32da88..51869aadd5 100644 --- a/src/rizin/RizinDebugSessionManager.ts +++ b/src/rizin/RizinDebugSessionManager.ts @@ -745,9 +745,9 @@ export class RizinDebugSessionManager { }, ), ); - session.pendingCommandCount += 1; const acceptedDeadline = this.#commandDeadline; const acceptedOutput = this.#commandOutput; + session.pendingCommandCount += 1; let release: (() => void) | undefined; let acquired = false; let commandSent = false; diff --git a/tests/boundary/providers/RizinDebugSessionAdmission.test.ts b/tests/boundary/providers/RizinDebugSessionAdmission.test.ts index 9c5a462599..3af7e82cda 100644 --- a/tests/boundary/providers/RizinDebugSessionAdmission.test.ts +++ b/tests/boundary/providers/RizinDebugSessionAdmission.test.ts @@ -4,6 +4,8 @@ import { expect, it } from "vitest"; import { RizinDebugSessionManager } from "../../../src/rizin/RizinDebugSessionManager.js"; import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { AnalysisSettings } from "../../../src/application/AnalysisSettings.js"; +import { createRizinCoverageHarness } from "./RizinDebugSessionCoverage.fixtures.js"; it.skipIf(process.platform === "win32")( "identifies the rejected TTY field and physical reason before spawning Rizin", @@ -44,3 +46,72 @@ it.skipIf(process.platform === "win32")( } }, ); + +it.skipIf(process.platform === "win32").each([ + ["debugger.command_deadline_ms", "REA_DEBUGGER_COMMAND_DEADLINE_MS", 5000], + [ + "debugger.max_command_output_bytes", + "REA_DEBUGGER_MAX_COMMAND_OUTPUT_BYTES", + 4096, + ], +] as const)( + "releases command capacity after an invalid %s is reset", + async (setting, environmentName, value) => { + const harness = createRizinCoverageHarness(); + const environment = { + [environmentName]: "invalid", + REA_DEBUGGER_MAX_PENDING_COMMANDS: "1", + }; + const settings = new AnalysisSettings(environment); + const set = () => + settings.update({ changes: [{ setting, action: "set", value }] }); + expect(set().ok).toBe(true); + const other = + setting === "debugger.command_deadline_ms" + ? "debugger.max_command_output_bytes" + : "debugger.command_deadline_ms"; + expect( + settings.update({ + changes: [{ setting: other, action: "set", value: 4096 }], + }).ok, + ).toBe(true); + try { + const provider = await harness.makeProvider(`process.stdout.write("\\0"); +let input = ""; +process.stdin.setEncoding("utf8"); +process.stdin.on("data", chunk => { + input += chunk; + for (;;) { + const end = input.indexOf("\\n"); if (end < 0) break; + const line = input.slice(0,end); input = input.slice(end+1); + if (line.startsWith("!echo ")) process.stdout.write(line.slice(6) + "\\0"); + else if (line === "dr") process.stdout.write("ok\\0"); + } +});`); + const { manager, sessionId } = await harness.start( + provider, + {}, + environment, + ); + expect( + (await settings.run(() => manager.execute(sessionId, "dr"))).ok, + ).toBe(true); + expect( + settings.update({ changes: [{ setting, action: "reset" }] }).ok, + ).toBe(true); + await expect( + settings.run(() => manager.execute(sessionId, "dr")), + ).rejects.toMatchObject({ _tag: "ConfigurationError" }); + expect(set().ok).toBe(true); + const retried = await settings.run(() => + manager.execute(sessionId, "dr"), + ); + expect(retried).toMatchObject({ + ok: true, + value: { result: { output: "ok" } }, + }); + } finally { + await harness.cleanup(); + } + }, +);