@@ -253,25 +253,38 @@ function parseOpenArgs(): DashboardOptions {
253253 } ;
254254}
255255
256- async function acquireSingleton ( options : DashboardOptions ) : Promise < net . Server > {
256+ type AcquireResult =
257+ | { role : 'winner' , server : net . Server }
258+ | { role : 'loser' , daemonPid : number } ;
259+
260+ async function acquireSingleton ( options : DashboardOptions ) : Promise < AcquireResult > {
257261 const socketPath = dashboardSocketPath ( ) ;
258262 if ( process . platform !== 'win32' )
259263 await fs . promises . mkdir ( path . dirname ( socketPath ) , { recursive : true } ) ;
260264
261265 return await new Promise ( ( resolve , reject ) => {
262266 const server = net . createServer ( ) ;
263- server . listen ( socketPath , ( ) => resolve ( server ) ) ;
267+ server . listen ( socketPath , ( ) => resolve ( { role : 'winner' , server } ) ) ;
264268 server . on ( 'error' , ( err : NodeJS . ErrnoException ) => {
265269 if ( err . code !== 'EADDRINUSE' )
266270 return reject ( err ) ;
271+ let ackBuffer = '' ;
267272 const client = net . connect ( socketPath , ( ) => {
268273 client . write ( JSON . stringify ( options ) + '\n' ) ;
269274 } ) ;
270- client . on ( 'end' , ( ) => reject ( new Error ( 'already running' ) ) ) ;
275+ client . on ( 'data' , chunk => { ackBuffer += chunk . toString ( ) ; } ) ;
276+ client . on ( 'end' , ( ) => {
277+ try {
278+ const { pid } = JSON . parse ( ackBuffer . trim ( ) ) ;
279+ resolve ( { role : 'loser' , daemonPid : pid } ) ;
280+ } catch ( e ) {
281+ reject ( e ) ;
282+ }
283+ } ) ;
271284 client . on ( 'error' , ( ) => {
272285 if ( process . platform !== 'win32' )
273286 fs . unlinkSync ( socketPath ) ;
274- server . listen ( socketPath , ( ) => resolve ( server ) ) ;
287+ server . listen ( socketPath , ( ) => resolve ( { role : 'winner' , server } ) ) ;
275288 } ) ;
276289 } ) ;
277290 } ) ;
@@ -304,24 +317,23 @@ export async function openDashboardApp() {
304317 // Self-destruct if the parent CLI dies before we signal READY. Unregistered
305318 // before we signal so the daemon outlives the parent.
306319 const stopSelfDestruct = selfDestructOnParentGone ( ) ;
307- let server : net . Server ;
308- try {
309- server = await acquireSingleton ( options ) ;
310- } catch {
320+ const acquired = await acquireSingleton ( options ) ;
321+ if ( acquired . role === 'loser' ) {
311322 // Another daemon is already running, signal success.
312323 stopSelfDestruct ( ) ;
313324 // eslint-disable-next-line no-console
314- console . log ( ' Dashboard is running' ) ;
325+ console . log ( ` Dashboard is running pid= ${ acquired . daemonPid } ` ) ;
315326 // eslint-disable-next-line no-restricted-properties
316327 await new Promise ( f => process . stdout . write ( '' , f ) ) ; // Make sure stdout is flushed.
317328 return ;
318329 }
330+ const { server } = acquired ;
319331 process . on ( 'exit' , ( ) => server . close ( ) ) ;
320332 try {
321333 await startApp ( server , options ) ;
322334 stopSelfDestruct ( ) ;
323335 // eslint-disable-next-line no-console
324- console . log ( ' Dashboard is running' ) ;
336+ console . log ( ` Dashboard is running pid= ${ process . pid } ` ) ;
325337 } catch ( error ) {
326338 // eslint-disable-next-line no-console
327339 console . log ( error ) ;
@@ -366,7 +378,7 @@ async function startApp(server: net.Server, options: DashboardOptions) {
366378 try {
367379 await page ?. bringToFront ( ) ;
368380 await dashboard . reveal ( parsed ) ;
369- socket . end ( ) ;
381+ socket . end ( JSON . stringify ( { pid : process . pid } ) + '\n' ) ;
370382 } catch ( e ) {
371383 socket . end ( e ) ;
372384 }
0 commit comments