@@ -28,85 +28,93 @@ async function main(): Promise<void> {
2828 process . exit ( 1 ) ;
2929 }
3030
31- const sessionStore = await SessionStore . load ( preflight . storageRoot , cwd ) ;
32- const port = await getPort ( { port : [ DEFAULT_PREF_PORT , 6801 , 6802 , 6803 , 6804 , 0 ] } ) ;
33- const auth = makeAuthContext ( port ) ;
31+ // The lock is owned from this point forward; release it on any failure so
32+ // a botched startup doesn't leave a stale lock blocking subsequent runs
33+ // until the staleness window expires.
34+ try {
35+ const sessionStore = await SessionStore . load ( preflight . storageRoot , cwd ) ;
36+ const port = await getPort ( { port : [ DEFAULT_PREF_PORT , 6801 , 6802 , 6803 , 6804 , 0 ] } ) ;
37+ const auth = makeAuthContext ( port ) ;
3438
35- const runManager = new RunManager ( {
36- claudeBin,
37- cwd,
38- storageRoot : preflight . storageRoot ,
39- session : sessionStore ,
40- } ) ;
41- await runManager . init ( ) ;
39+ const runManager = new RunManager ( {
40+ claudeBin,
41+ cwd,
42+ storageRoot : preflight . storageRoot ,
43+ session : sessionStore ,
44+ } ) ;
45+ await runManager . init ( ) ;
4246
43- const webRoot = resolveWebRoot ( ) ;
44- const handler = createRouter ( { auth, session : sessionStore , runManager, webRoot, cwd } ) ;
47+ const webRoot = resolveWebRoot ( ) ;
48+ const handler = createRouter ( { auth, session : sessionStore , runManager, webRoot, cwd } ) ;
4549
46- const server = createServer ( ( req , res ) => {
47- Promise . resolve ( handler ( req , res ) ) . catch ( ( err ) => {
48- log . error ( 'http.handler-rejected' , { error : ( err as Error ) . message } ) ;
49- try {
50- res . statusCode = 500 ;
51- res . end ( ) ;
52- } catch {
53- /* */
54- }
50+ const server = createServer ( ( req , res ) => {
51+ Promise . resolve ( handler ( req , res ) ) . catch ( ( err ) => {
52+ log . error ( 'http.handler-rejected' , { error : ( err as Error ) . message } ) ;
53+ try {
54+ res . statusCode = 500 ;
55+ res . end ( ) ;
56+ } catch {
57+ /* */
58+ }
59+ } ) ;
5560 } ) ;
56- } ) ;
5761
58- server . listen ( port , '127.0.0.1' , async ( ) => {
59- await writeLockMeta ( preflight . lockFilePath , process . pid , port ) ;
60- const url = `${ auth . origin } /?t=${ auth . token } ` ;
61- log . info ( 'server.listening' , { url, pid : process . pid } ) ;
62- console . log ( `mdredd listening at ${ url } ` ) ;
63- if ( shouldOpen ) {
64- open ( url ) . catch ( ( err ) => {
65- console . log ( `(could not open browser automatically: ${ err . message } )` ) ;
66- } ) ;
67- }
68- } ) ;
62+ server . listen ( port , '127.0.0.1' , async ( ) => {
63+ await writeLockMeta ( preflight . lockFilePath , process . pid , port ) ;
64+ const url = `${ auth . origin } /?t=${ auth . token } ` ;
65+ log . info ( 'server.listening' , { url, pid : process . pid } ) ;
66+ console . log ( `mdredd listening at ${ url } ` ) ;
67+ if ( shouldOpen ) {
68+ open ( url ) . catch ( ( err ) => {
69+ console . log ( `(could not open browser automatically: ${ err . message } )` ) ;
70+ } ) ;
71+ }
72+ } ) ;
6973
70- // 5s for runners to drain (each runner self-bounds at SIGTERM+2s SIGKILL),
71- // plus 3s slack for lockfile / FS work. Anything still hanging past the hard
72- // timer is force-exited so a stuck child can never wedge the server.
73- const STOP_RUNNERS_TIMEOUT_MS = 5_000 ;
74- const HARD_SHUTDOWN_TIMEOUT_MS = 8_000 ;
74+ // 5s for runners to drain (each runner self-bounds at SIGTERM+2s SIGKILL),
75+ // plus 3s slack for lockfile / FS work. Anything still hanging past the hard
76+ // timer is force-exited so a stuck child can never wedge the server.
77+ const STOP_RUNNERS_TIMEOUT_MS = 5_000 ;
78+ const HARD_SHUTDOWN_TIMEOUT_MS = 8_000 ;
7579
76- let shuttingDown = false ;
77- const shutdown = async ( sig : string ) : Promise < void > => {
78- if ( shuttingDown ) return ;
79- shuttingDown = true ;
80- log . info ( 'server.shutdown' , { signal : sig , activeRuns : runManager . activeCount ( ) } ) ;
81- const hardTimer = setTimeout ( ( ) => {
82- log . error ( 'server.shutdown-forced-exit' , { reason : 'hard timeout exceeded' } ) ;
83- process . exit ( 1 ) ;
84- } , HARD_SHUTDOWN_TIMEOUT_MS ) ;
85- hardTimer . unref ( ) ;
86- try {
87- // Stop accepting new HTTP traffic and drop existing keep-alive/SSE
88- // connections so the server's `listening` socket and the SSE keepers
89- // don't hold the event loop open.
90- server . close ( ) ;
91- server . closeAllConnections ?.( ) ;
92- const result = await runManager . stopAll ( STOP_RUNNERS_TIMEOUT_MS ) ;
93- if ( result . timedOut ) {
94- log . warn ( 'server.shutdown.stopAll-timeout' , { stopped : result . stopped } ) ;
80+ let shuttingDown = false ;
81+ const shutdown = async ( sig : string ) : Promise < void > => {
82+ if ( shuttingDown ) return ;
83+ shuttingDown = true ;
84+ log . info ( 'server.shutdown' , { signal : sig , activeRuns : runManager . activeCount ( ) } ) ;
85+ const hardTimer = setTimeout ( ( ) => {
86+ log . error ( 'server.shutdown-forced-exit' , { reason : 'hard timeout exceeded' } ) ;
87+ process . exit ( 1 ) ;
88+ } , HARD_SHUTDOWN_TIMEOUT_MS ) ;
89+ hardTimer . unref ( ) ;
90+ try {
91+ // Stop accepting new HTTP traffic and drop existing keep-alive/SSE
92+ // connections so the server's `listening` socket and the SSE keepers
93+ // don't hold the event loop open.
94+ server . close ( ) ;
95+ server . closeAllConnections ?.( ) ;
96+ const result = await runManager . stopAll ( STOP_RUNNERS_TIMEOUT_MS ) ;
97+ if ( result . timedOut ) {
98+ log . warn ( 'server.shutdown.stopAll-timeout' , { stopped : result . stopped } ) ;
99+ }
100+ await preflight . releaseLock ( ) ;
101+ } catch ( err ) {
102+ log . error ( 'server.shutdown-error' , { error : ( err as Error ) . message } ) ;
103+ } finally {
104+ clearTimeout ( hardTimer ) ;
95105 }
96- await preflight . releaseLock ( ) ;
97- } catch ( err ) {
98- log . error ( 'server.shutdown-error' , { error : ( err as Error ) . message } ) ;
99- } finally {
100- clearTimeout ( hardTimer ) ;
101- }
102- process . exit ( 0 ) ;
103- } ;
104- process . on ( 'SIGINT' , ( ) => {
105- void shutdown ( 'SIGINT' ) ;
106- } ) ;
107- process . on ( 'SIGTERM' , ( ) => {
108- void shutdown ( 'SIGTERM' ) ;
109- } ) ;
106+ process . exit ( 0 ) ;
107+ } ;
108+ process . on ( 'SIGINT' , ( ) => {
109+ void shutdown ( 'SIGINT' ) ;
110+ } ) ;
111+ process . on ( 'SIGTERM' , ( ) => {
112+ void shutdown ( 'SIGTERM' ) ;
113+ } ) ;
114+ } catch ( err ) {
115+ await preflight . releaseLock ( ) . catch ( ( ) => undefined ) ;
116+ throw err ;
117+ }
110118}
111119
112120function resolveWebRoot ( ) : string {
0 commit comments