@@ -194,11 +194,66 @@ def delayed_exit():
194
194
threading .Thread (target = delayed_exit , daemon = True ).start ()
195
195
196
196
197
+ class NoopLifespan :
198
+ """A no-operation lifespan implementation that provides the required methods but doesn't do anything."""
199
+
200
+ def __init__ (self , app ):
201
+ self .app = app
202
+
203
+ async def startup (self ):
204
+ """No-op startup method."""
205
+ logger .warning ("Using NoopLifespan - server may not handle startup/shutdown events properly" )
206
+ pass
207
+
208
+ async def shutdown (self ):
209
+ """No-op shutdown method."""
210
+ pass
211
+
212
+
197
213
class ServerWithCallback (uvicorn .Server ):
198
214
def install_signal_handlers (self ):
199
215
# Override to prevent uvicorn from installing its own handlers
200
216
pass
201
217
218
+ async def startup (self , sockets = None ):
219
+ """Override the startup method to add error handling for lifespan startup."""
220
+ if self .should_exit :
221
+ return
222
+
223
+ if sockets is not None :
224
+ self .servers = []
225
+ for socket in sockets :
226
+ server = await self .config .server_class (config = self .config , server = self )
227
+ await server .start ()
228
+ self .servers .append (server )
229
+ else :
230
+ self .servers = [await self .config .server_class (config = self .config , server = self )]
231
+ await self .servers [0 ].start ()
232
+
233
+ if self .lifespan is not None :
234
+ try :
235
+ await self .lifespan .startup ()
236
+ except Exception as e :
237
+ logger .error (f"Error during lifespan startup: { str (e )} " )
238
+ logger .debug (f"Lifespan startup error details: { traceback .format_exc ()} " )
239
+ # Replace with NoopLifespan if startup fails
240
+ self .lifespan = NoopLifespan (self .config .app )
241
+ logger .warning ("Replaced failed lifespan with NoopLifespan" )
242
+
243
+ async def shutdown (self , sockets = None ):
244
+ """Override the shutdown method to add error handling for lifespan shutdown."""
245
+ if self .servers :
246
+ for server in self .servers :
247
+ await server .shutdown ()
248
+
249
+ if self .lifespan is not None :
250
+ try :
251
+ await self .lifespan .shutdown ()
252
+ except Exception as e :
253
+ logger .error (f"Error during lifespan shutdown: { str (e )} " )
254
+ logger .debug (f"Lifespan shutdown error details: { traceback .format_exc ()} " )
255
+ logger .warning ("Continuing shutdown despite lifespan error" )
256
+
202
257
async def serve (self , sockets = None ):
203
258
self .config .setup_event_loop ()
204
259
@@ -296,19 +351,25 @@ async def serve(self, sockets=None):
296
351
logger .info ("Using LifespanState from uvicorn.lifespan.state" )
297
352
except (ImportError , AttributeError , TypeError ) as e :
298
353
logger .debug (f"Failed to import or initialize LifespanState: { str (e )} " )
299
- # Fallback to no lifespan
300
- self .lifespan = None
301
- logger .warning ("Could not initialize lifespan - server may not handle startup/shutdown events properly" )
302
-
303
- await self .startup (sockets = sockets )
354
+ # Fallback to NoopLifespan
355
+ self .lifespan = NoopLifespan (self .config .app )
356
+ logger .warning ("Using NoopLifespan - server may not handle startup/shutdown events properly" )
304
357
305
- # Call our callback before processing requests
306
- # We need to access the on_startup function from the outer scope
307
- if hasattr (self , 'on_startup_callback' ) and callable (self .on_startup_callback ):
308
- self .on_startup_callback ()
309
-
310
- await self .main_loop ()
311
- await self .shutdown ()
358
+ try :
359
+ await self .startup (sockets = sockets )
360
+
361
+ # Call our callback before processing requests
362
+ # We need to access the on_startup function from the outer scope
363
+ if hasattr (self , 'on_startup_callback' ) and callable (self .on_startup_callback ):
364
+ self .on_startup_callback ()
365
+
366
+ await self .main_loop ()
367
+ await self .shutdown ()
368
+ except Exception as e :
369
+ logger .error (f"Error during server operation: { str (e )} " )
370
+ logger .debug (f"Server error details: { traceback .format_exc ()} " )
371
+ # Re-raise to allow proper error handling
372
+ raise
312
373
313
374
314
375
def start_server (use_ngrok : bool = None , port : int = None , ngrok_auth_token : Optional [str ] = None ):
0 commit comments