@@ -188,7 +188,22 @@ def signal_handler(signum, frame):
188
188
189
189
# Exit after a short delay to allow cleanup
190
190
def delayed_exit ():
191
- time .sleep (2 ) # Give some time for cleanup
191
+ # Give more time for cleanup - increased from 2 to 5 seconds
192
+ # This allows the server to complete its shutdown process
193
+ time .sleep (5 )
194
+
195
+ # Check if we need to force exit
196
+ try :
197
+ # Import here to avoid circular imports
198
+ from .core .app import app
199
+ if hasattr (app , "state" ) and hasattr (app .state , "server" ) and app .state .server :
200
+ logger .debug ("Server still running after timeout, forcing exit" )
201
+ else :
202
+ logger .debug ("Server shutdown completed successfully" )
203
+ except Exception :
204
+ pass
205
+
206
+ # Force exit if we're still running
192
207
sys .exit (0 )
193
208
194
209
threading .Thread (target = delayed_exit , daemon = True ).start ()
@@ -479,7 +494,16 @@ async def serve(self, sock=None):
479
494
class ServerWithCallback (uvicorn .Server ):
480
495
def install_signal_handlers (self ):
481
496
# Override to prevent uvicorn from installing its own handlers
482
- pass
497
+ # but still handle the should_exit flag for clean shutdown
498
+
499
+ def handle_exit (signum , frame ):
500
+ self .should_exit = True
501
+ logger .debug (f"Signal { signum } received in ServerWithCallback, setting should_exit=True" )
502
+
503
+ # Register our own minimal signal handlers that just set should_exit
504
+ # This allows the main process signal handler to handle the actual shutdown
505
+ signal .signal (signal .SIGINT , handle_exit )
506
+ signal .signal (signal .SIGTERM , handle_exit )
483
507
484
508
async def startup (self , sockets = None ):
485
509
"""Override the startup method to add error handling for lifespan startup."""
@@ -536,34 +560,50 @@ async def main_loop(self):
536
560
try :
537
561
# Use asyncio.sleep to keep the server running
538
562
while not self .should_exit :
539
- await asyncio .sleep (0.1 )
563
+ # Check more frequently to respond to shutdown signals faster
564
+ await asyncio .sleep (0.05 )
565
+
566
+ # Check if we've received a shutdown signal
567
+ if self .should_exit :
568
+ logger .debug ("Shutdown signal detected in main_loop" )
569
+ break
540
570
except Exception as e :
541
571
logger .error (f"Error in main loop: { str (e )} " )
542
572
logger .debug (f"Main loop error details: { traceback .format_exc ()} " )
543
573
# Set should_exit to True to initiate shutdown
544
574
self .should_exit = True
575
+
576
+ logger .debug ("Exiting main_loop" )
545
577
546
578
async def shutdown (self , sockets = None ):
547
579
"""Override the shutdown method to add error handling for lifespan shutdown."""
580
+ logger .debug ("Starting server shutdown process" )
581
+
582
+ # First, shut down all servers
548
583
if self .servers :
549
584
for server in self .servers :
550
585
try :
551
- # Check if the server has a shutdown method
552
- if hasattr (server , 'shutdown' ):
553
- await server .shutdown ()
554
- else :
555
- logger .warning (f"Server object { type (server )} has no shutdown method, skipping" )
586
+ server .close ()
587
+ await server .wait_closed ()
588
+ logger .debug ("Server closed successfully" )
556
589
except Exception as e :
557
- logger .error (f"Error shutting down server: { str (e )} " )
558
- logger .debug (f"Server shutdown error details: { traceback .format_exc ()} " )
590
+ logger .error (f"Error closing server: { str (e )} " )
591
+ logger .debug (f"Server close error details: { traceback .format_exc ()} " )
559
592
593
+ # Then, shut down the lifespan
560
594
if self .lifespan is not None :
561
595
try :
562
596
await self .lifespan .shutdown ()
597
+ logger .debug ("Lifespan shutdown completed" )
563
598
except Exception as e :
564
599
logger .error (f"Error during lifespan shutdown: { str (e )} " )
565
600
logger .debug (f"Lifespan shutdown error details: { traceback .format_exc ()} " )
566
- logger .warning ("Continuing shutdown despite lifespan error" )
601
+
602
+ # Clear all references to help with garbage collection
603
+ self .servers = []
604
+ self .lifespan = None
605
+
606
+ logger .debug ("Server shutdown process completed" )
567
607
568
608
async def serve (self , sockets = None ):
569
609
self .config .setup_event_loop ()
0 commit comments