дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
A Surefire/Failsafe fork started with suspend=y never reaches the console with its "Listening for transport" line: surefire buffers the fork's output until the tests run, and the tests wait for the debugger. The IDE found no port, never attached, and Stop left an orphan debug session. Pick a free port up front (new debug_free_port IPC), pass it to the -Dmaven.surefire.debug/-Dmaven.failsafe.debug agent as address=127.0.0.1:<port>, and attach right away — java-debug retries the connect while the fork starts, so no output line is needed. A duplicated line that does arrive later is deduplicated by port.
Цей коміт міститься в:
1 джерело
92b527ab0e
коміт
0ccc1e687d
6 files changed
+49
-6
No files matched your search
@@ -343,6 +343,14 @@ describe("debugging build tool configurations", () => {
|
||||
expect(merged.args[1]).toMatch(/^-Dspring-boot\.run\.jvmArguments=-Xmx1g -agentlib:jdwp=/);
|
||||
const test = jdwpLaunch(cfg({ id: "t", kind: "maven", args: ["test", "-Dtest=A#b"], isTest: true }))!;
|
||||
expect(test.args.some((a) => a.startsWith("-Dmaven.surefire.debug=-agentlib:jdwp="))).toBe(true);
|
||||
// A fork's "Listening for transport" line stays buffered until tests run — which suspend=y
|
||||
// prevents — so a Maven test's port is pinned and attached without the line.
|
||||
const pinned = jdwpLaunch(cfg({ id: "t2", kind: "maven", args: ["test"], isTest: true }), 50123)!;
|
||||
expect(pinned.args.some((a) => a.includes("address=127.0.0.1:50123"))).toBe(true);
|
||||
const pinnedFailsafe = jdwpLaunch(cfg({ id: "t3", kind: "maven", args: ["verify"], isTest: true }), 50124)!;
|
||||
expect(
|
||||
pinnedFailsafe.args.some((a) => a.startsWith("-Dmaven.failsafe.debug=") && a.includes("address=127.0.0.1:50124")),
|
||||
).toBe(true);
|
||||
expect(jdwpLaunch(cfg({ id: "g", kind: "gradle", args: ["bootRun"] }))!.args).toContain(DELEGATED_DEBUG);
|
||||
const gradleTest = jdwpLaunch(cfg({ id: "gt", kind: "gradle", args: ["test"], isTest: true }))!;
|
||||
expect(gradleTest.args).toContain(DELEGATED_DEBUG);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* debugger (a script such as `catalina.sh run`) through `JAVA_TOOL_OPTIONS`, which every JVM reads.
|
||||
*/
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { call, commands, type RunConfig } from "@/platform/ipc";
|
||||
import { isJvmApplication, JDWP_AGENT, jdwpListeningPort } from "@/platform/javaLaunch";
|
||||
import { applyBuildPolicyToCommand } from "./buildCommand";
|
||||
import { startRun, useRun } from "./store";
|
||||
@@ -37,8 +37,11 @@ function withProperty(args: readonly string[], key: string, value: string): stri
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The configuration with a waiting JDWP agent, or null when it starts no debuggable JVM. */
|
||||
export function jdwpLaunch(config: RunConfig): RunConfig | null {
|
||||
/** The configuration with a waiting JDWP agent, or null when it starts no debuggable JVM.
|
||||
* `jdwpPort`: pin a Surefire / Failsafe fork to this port — its "Listening for transport"
|
||||
* line stays buffered until the tests run, which `suspend=y` prevents, so the port must be
|
||||
* known without it. */
|
||||
export function jdwpLaunch(config: RunConfig, jdwpPort?: number): RunConfig | null {
|
||||
config = applyBuildPolicyToCommand(config);
|
||||
if (config.kind === "tomcat") return { ...config, params: { ...config.params, tomcatDebug: "true" } };
|
||||
const args = config.args;
|
||||
@@ -53,8 +56,9 @@ export function jdwpLaunch(config: RunConfig): RunConfig | null {
|
||||
return { ...config, args: withProperty(args, "spring-boot.run.jvmArguments", JDWP_AGENT) };
|
||||
if (config.isTest || args.some(isMavenTestGoal)) {
|
||||
const failsafe = args.some((a) => /integration-test|verify|failsafe/.test(a));
|
||||
let out = withProperty(args, "maven.surefire.debug", JDWP_AGENT);
|
||||
if (failsafe) out = withProperty(out, "maven.failsafe.debug", JDWP_AGENT);
|
||||
const agent = jdwpPort ? JDWP_AGENT.replace("address=127.0.0.1:0", `address=127.0.0.1:${jdwpPort}`) : JDWP_AGENT;
|
||||
let out = withProperty(args, "maven.surefire.debug", agent);
|
||||
if (failsafe) out = withProperty(out, "maven.failsafe.debug", agent);
|
||||
return { ...config, args: out };
|
||||
}
|
||||
if (JVM_ADAPTERS.includes(config.debugAdapter ?? "") || args.some(isMavenInProcessGoal))
|
||||
@@ -80,13 +84,23 @@ export function jdwpLaunch(config: RunConfig): RunConfig | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** A Maven test launch: its Surefire / Failsafe fork holds the "Listening" line, so pin its JDWP port. */
|
||||
function needsPinnedPort(config: RunConfig): boolean {
|
||||
if (config.kind !== "maven" && !/(^|\/)mvnw?(\.cmd)?$/.test(config.command)) return false;
|
||||
return config.isTest || config.args.some(isMavenTestGoal);
|
||||
}
|
||||
|
||||
/** Runs `config` with a JDWP agent and attaches the Java debugger to every JVM it starts. */
|
||||
export async function debugWithAgent(config: RunConfig, show?: boolean): Promise<boolean> {
|
||||
const launch = jdwpLaunch(config);
|
||||
const pinned = needsPinnedPort(config) ? await call(commands.debugFreePort()).catch(() => null) : null;
|
||||
const launch = jdwpLaunch(config, pinned ?? undefined);
|
||||
if (!launch) return false;
|
||||
let sessionId: string | null = null;
|
||||
const waiting: number[] = [];
|
||||
const attached = new Set<number>();
|
||||
const attach = (port: number) => {
|
||||
if (attached.has(port)) return;
|
||||
attached.add(port);
|
||||
const runId = useRun.getState().sessions.find((s) => s.id === sessionId)?.runId ?? null;
|
||||
void executeAction("debug.attach", {
|
||||
name: config.name,
|
||||
@@ -111,6 +125,8 @@ export async function debugWithAgent(config: RunConfig, show?: boolean): Promise
|
||||
});
|
||||
if (!session) return true;
|
||||
sessionId = session.id;
|
||||
// The pinned port needs no output line: java-debug retries the attach while the fork starts.
|
||||
if (pinned !== null) attach(pinned);
|
||||
waiting.splice(0).forEach(attach);
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача