@@ -49,22 +49,22 @@ export async function registerNoConfigDebug(
4949 if ( ! fs . existsSync ( tempDirPath ) ) {
5050 fs . mkdirSync ( tempDirPath , { recursive : true } ) ;
5151 } else {
52- // remove endpoint file in the temp directory if it exists
52+ // remove endpoint file in the temp directory if it exists (async to avoid blocking)
5353 if ( fs . existsSync ( tempFilePath ) ) {
54- fs . unlinkSync ( tempFilePath ) ;
54+ fs . promises . unlink ( tempFilePath ) . catch ( ( err ) => {
55+ console . error ( `[Java Debug] Failed to cleanup old endpoint file: ${ err } ` ) ;
56+ } ) ;
5557 }
5658 }
5759
5860 // clear the env var collection to remove any existing env vars
5961 collection . clear ( ) ;
6062
61- // Add env vars for VSCODE_JDWP_ADAPTER_ENDPOINTS and JAVA_TOOL_OPTIONS
63+ // Add env var for VSCODE_JDWP_ADAPTER_ENDPOINTS
64+ // Note: We do NOT set JAVA_TOOL_OPTIONS globally to avoid affecting all Java processes
65+ // (javac, maven, gradle, language server, etc.). Instead, JAVA_TOOL_OPTIONS is set
66+ // only in the javadebug wrapper scripts (javadebug.ps1, javadebug.bat, javadebug)
6267 collection . replace ( 'VSCODE_JDWP_ADAPTER_ENDPOINTS' , tempFilePath ) ;
63-
64- // Configure JDWP to listen on a random port and suspend until debugger attaches
65- // quiet=y prevents the "Listening for transport..." message from appearing in terminal
66- collection . replace ( 'JAVA_TOOL_OPTIONS' ,
67- '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0,quiet=y' ) ;
6868
6969 const noConfigScriptsDir = path . join ( extPath , 'bundled' , 'scripts' , 'noConfigScripts' ) ;
7070 const pathSeparator = process . platform === 'win32' ? ';' : ':' ;
@@ -81,10 +81,19 @@ export async function registerNoConfigDebug(
8181 new vscode . RelativePattern ( tempDirPath , '**/*.txt' )
8282 ) ;
8383
84- const fileCreationEvent = fileSystemWatcher . onDidCreate ( async ( uri ) => {
84+ // Track active debug sessions to prevent duplicates
85+ const activeDebugSessions = new Set < number > ( ) ;
86+
87+ // Handle both file creation and modification to support multiple runs
88+ const handleEndpointFile = async ( uri : vscode . Uri ) => {
8589 console . log ( '[Java Debug] No-config debug session detected' ) ;
8690
8791 const filePath = uri . fsPath ;
92+
93+ // Add a small delay to ensure file is fully written
94+ // File system events can fire before write is complete
95+ await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
96+
8897 fs . readFile ( filePath , ( err , data ) => {
8998 if ( err ) {
9099 console . error ( `[Java Debug] Error reading endpoint file: ${ err } ` ) ;
@@ -94,8 +103,31 @@ export async function registerNoConfigDebug(
94103 // parse the client port
95104 const dataParse = data . toString ( ) ;
96105 const jsonData = JSON . parse ( dataParse ) ;
97- const clientPort = jsonData . client ?. port ;
106+
107+ // Validate JSON structure
108+ if ( ! jsonData || typeof jsonData !== 'object' || ! jsonData . client ) {
109+ console . error ( `[Java Debug] Invalid endpoint file format: ${ dataParse } ` ) ;
110+ return ;
111+ }
112+
113+ const clientPort = jsonData . client . port ;
114+
115+ // Validate port number
116+ if ( ! clientPort || typeof clientPort !== 'number' || clientPort < 1 || clientPort > 65535 ) {
117+ console . error ( `[Java Debug] Invalid port number: ${ clientPort } ` ) ;
118+ return ;
119+ }
120+
121+ // Check if we already have an active session for this port
122+ if ( activeDebugSessions . has ( clientPort ) ) {
123+ console . log ( `[Java Debug] Debug session already active for port ${ clientPort } , skipping` ) ;
124+ return ;
125+ }
126+
98127 console . log ( `[Java Debug] Parsed JDWP port: ${ clientPort } ` ) ;
128+
129+ // Mark this port as active
130+ activeDebugSessions . add ( clientPort ) ;
99131
100132 const options : vscode . DebugSessionOptions = {
101133 noDebug : false ,
@@ -116,24 +148,52 @@ export async function registerNoConfigDebug(
116148 ( started ) => {
117149 if ( started ) {
118150 console . log ( '[Java Debug] Successfully started no-config debug session' ) ;
151+ // Clean up the endpoint file after successful debug session start (async)
152+ if ( fs . existsSync ( filePath ) ) {
153+ fs . promises . unlink ( filePath ) . then ( ( ) => {
154+ console . log ( '[Java Debug] Cleaned up endpoint file' ) ;
155+ } ) . catch ( ( cleanupErr ) => {
156+ console . error ( `[Java Debug] Failed to cleanup endpoint file: ${ cleanupErr } ` ) ;
157+ } ) ;
158+ }
119159 } else {
120160 console . error ( '[Java Debug] Error starting debug session, session not started.' ) ;
161+ // Remove from active sessions on failure
162+ activeDebugSessions . delete ( clientPort ) ;
121163 }
122164 } ,
123165 ( error ) => {
124166 console . error ( `[Java Debug] Error starting debug session: ${ error } ` ) ;
167+ // Remove from active sessions on error
168+ activeDebugSessions . delete ( clientPort ) ;
125169 } ,
126170 ) ;
127171 } catch ( parseErr ) {
128172 console . error ( `[Java Debug] Error parsing JSON: ${ parseErr } ` ) ;
129173 }
130174 } ) ;
175+ } ;
176+
177+ // Listen for both file creation and modification events
178+ const fileCreationEvent = fileSystemWatcher . onDidCreate ( handleEndpointFile ) ;
179+ const fileChangeEvent = fileSystemWatcher . onDidChange ( handleEndpointFile ) ;
180+
181+ // Clean up active sessions when debug session ends
182+ const debugSessionEndListener = vscode . debug . onDidTerminateDebugSession ( ( session ) => {
183+ if ( session . name === 'Attach to Java (No-Config)' && session . configuration . port ) {
184+ const port = session . configuration . port ;
185+ activeDebugSessions . delete ( port ) ;
186+ console . log ( `[Java Debug] Debug session ended for port ${ port } ` ) ;
187+ }
131188 } ) ;
132189
133190 return Promise . resolve (
134191 new vscode . Disposable ( ( ) => {
135192 fileSystemWatcher . dispose ( ) ;
136193 fileCreationEvent . dispose ( ) ;
194+ fileChangeEvent . dispose ( ) ;
195+ debugSessionEndListener . dispose ( ) ;
196+ activeDebugSessions . clear ( ) ;
137197 } ) ,
138198 ) ;
139199}
0 commit comments