@@ -195,9 +195,10 @@ def delayed_exit():
195
195
196
196
197
197
class NoopLifespan :
198
- """A no-operation lifespan implementation that provides the required methods but doesn't do anything ."""
198
+ """A no-op lifespan implementation for when all lifespan initialization attempts fail ."""
199
199
200
200
def __init__ (self , app ):
201
+ """Initialize with the app."""
201
202
self .app = app
202
203
203
204
async def startup (self ):
@@ -210,6 +211,151 @@ async def shutdown(self):
210
211
pass
211
212
212
213
214
+ class SimpleTCPServer :
215
+ """A simple TCP server implementation for when TCPServer import fails."""
216
+
217
+ def __init__ (self , config ):
218
+ """Initialize with the config."""
219
+ self .config = config
220
+ self .server = None
221
+ self .started = False
222
+ self ._serve_task = None
223
+ self ._socket = None
224
+ self ._running = False
225
+
226
+ async def start (self ):
227
+ """Start the server."""
228
+ self .started = True
229
+ logger .info ("Started SimpleTCPServer as fallback" )
230
+
231
+ # Create a task to run the server
232
+ if not self ._serve_task :
233
+ self ._serve_task = asyncio .create_task (self ._run_server ())
234
+
235
+ async def _run_server (self ):
236
+ """Run the server in a separate task."""
237
+ try :
238
+ self ._running = True
239
+
240
+ # Try to create a socket
241
+ import socket
242
+ host = self .config .host
243
+ port = self .config .port
244
+
245
+ self ._socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
246
+ self ._socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
247
+
248
+ try :
249
+ self ._socket .bind ((host , port ))
250
+ self ._socket .listen (100 ) # Backlog
251
+ self ._socket .setblocking (False )
252
+
253
+ logger .info (f"SimpleTCPServer listening on { host } :{ port } " )
254
+
255
+ # Create a simple HTTP server
256
+ loop = asyncio .get_event_loop ()
257
+
258
+ while self ._running :
259
+ try :
260
+ client_socket , addr = await loop .sock_accept (self ._socket )
261
+ logger .debug (f"Connection from { addr } " )
262
+
263
+ # Handle the connection in a separate task
264
+ asyncio .create_task (self ._handle_connection (client_socket ))
265
+ except asyncio .CancelledError :
266
+ break
267
+ except Exception as e :
268
+ logger .error (f"Error accepting connection: { str (e )} " )
269
+ finally :
270
+ if self ._socket :
271
+ self ._socket .close ()
272
+ self ._socket = None
273
+ except Exception as e :
274
+ logger .error (f"Error in SimpleTCPServer._run_server: { str (e )} " )
275
+ logger .debug (f"SimpleTCPServer._run_server error details: { traceback .format_exc ()} " )
276
+ finally :
277
+ self ._running = False
278
+
279
+ async def _handle_connection (self , client_socket ):
280
+ """Handle a client connection."""
281
+ try :
282
+ loop = asyncio .get_event_loop ()
283
+
284
+ # Set non-blocking mode
285
+ client_socket .setblocking (False )
286
+
287
+ # Read the request
288
+ request_data = b""
289
+ while True :
290
+ try :
291
+ chunk = await loop .sock_recv (client_socket , 4096 )
292
+ if not chunk :
293
+ break
294
+ request_data += chunk
295
+
296
+ # Check if we've received the end of the HTTP request
297
+ if b"\r \n \r \n " in request_data :
298
+ break
299
+ except Exception :
300
+ break
301
+
302
+ # Prepare a simple HTTP response
303
+ response = (
304
+ b"HTTP/1.1 200 OK\r \n "
305
+ b"Content-Type: text/plain\r \n "
306
+ b"Connection: close\r \n "
307
+ b"\r \n "
308
+ b"LocalLab server is running (fallback mode)"
309
+ )
310
+
311
+ # Send the response
312
+ await loop .sock_sendall (client_socket , response )
313
+ except Exception as e :
314
+ logger .error (f"Error handling connection: { str (e )} " )
315
+ finally :
316
+ try :
317
+ client_socket .close ()
318
+ except Exception :
319
+ pass
320
+
321
+ async def shutdown (self ):
322
+ """Shutdown the server."""
323
+ self .started = False
324
+ self ._running = False
325
+
326
+ # Cancel the serve task
327
+ if self ._serve_task :
328
+ self ._serve_task .cancel ()
329
+ try :
330
+ await self ._serve_task
331
+ except asyncio .CancelledError :
332
+ pass
333
+ self ._serve_task = None
334
+
335
+ # Close the socket
336
+ if self ._socket :
337
+ try :
338
+ self ._socket .close ()
339
+ except Exception :
340
+ pass
341
+ self ._socket = None
342
+
343
+ logger .info ("Shutdown SimpleTCPServer" )
344
+
345
+ async def serve (self , sock = None ):
346
+ """Serve the application."""
347
+ self .started = True
348
+ try :
349
+ # Keep the server running until shutdown is called
350
+ while self .started :
351
+ await asyncio .sleep (1 )
352
+ except Exception as e :
353
+ logger .error (f"Error in SimpleTCPServer.serve: { str (e )} " )
354
+ logger .debug (f"SimpleTCPServer.serve error details: { traceback .format_exc ()} " )
355
+ finally :
356
+ self .started = False
357
+
358
+
213
359
class ServerWithCallback (uvicorn .Server ):
214
360
def install_signal_handlers (self ):
215
361
# Override to prevent uvicorn from installing its own handlers
@@ -237,11 +383,20 @@ async def startup(self, sockets=None):
237
383
except (ImportError , AttributeError ):
238
384
# Last resort - use a simple server implementation
239
385
logger .warning ("Could not import TCPServer - using simple server implementation" )
240
- from uvicorn . protocols . http . auto import AutoHTTPProtocol
241
- server = uvicorn . Server (config = self .config )
386
+ # Use our custom SimpleTCPServer instead of uvicorn.Server
387
+ server = SimpleTCPServer (config = self .config )
242
388
243
389
server .server = self # Set the server reference
244
- await server .start ()
390
+ # Check if the server has a start method, otherwise use serve
391
+ if hasattr (server , 'start' ):
392
+ await server .start ()
393
+ elif hasattr (server , 'serve' ):
394
+ # Create a task for serve but don't await it directly
395
+ # as it would block indefinitely
396
+ asyncio .create_task (server .serve ())
397
+ else :
398
+ logger .error (f"Server object { type (server )} has neither start() nor serve() method" )
399
+ raise RuntimeError ("Incompatible server implementation" )
245
400
self .servers .append (server )
246
401
else :
247
402
# Use TCPServer directly instead of relying on config.server_class
@@ -257,11 +412,20 @@ async def startup(self, sockets=None):
257
412
except (ImportError , AttributeError ):
258
413
# Last resort - use a simple server implementation
259
414
logger .warning ("Could not import TCPServer - using simple server implementation" )
260
- from uvicorn . protocols . http . auto import AutoHTTPProtocol
261
- server = uvicorn . Server (config = self .config )
415
+ # Use our custom SimpleTCPServer instead of uvicorn.Server
416
+ server = SimpleTCPServer (config = self .config )
262
417
263
418
server .server = self # Set the server reference
264
- await server .start ()
419
+ # Check if the server has a start method, otherwise use serve
420
+ if hasattr (server , 'start' ):
421
+ await server .start ()
422
+ elif hasattr (server , 'serve' ):
423
+ # Create a task for serve but don't await it directly
424
+ # as it would block indefinitely
425
+ asyncio .create_task (server .serve ())
426
+ else :
427
+ logger .error (f"Server object { type (server )} has neither start() nor serve() method" )
428
+ raise RuntimeError ("Incompatible server implementation" )
265
429
self .servers = [server ]
266
430
267
431
if self .lifespan is not None :
@@ -290,7 +454,15 @@ async def shutdown(self, sockets=None):
290
454
"""Override the shutdown method to add error handling for lifespan shutdown."""
291
455
if self .servers :
292
456
for server in self .servers :
293
- await server .shutdown ()
457
+ try :
458
+ # Check if the server has a shutdown method
459
+ if hasattr (server , 'shutdown' ):
460
+ await server .shutdown ()
461
+ else :
462
+ logger .warning (f"Server object { type (server )} has no shutdown method, skipping" )
463
+ except Exception as e :
464
+ logger .error (f"Error shutting down server: { str (e )} " )
465
+ logger .debug (f"Server shutdown error details: { traceback .format_exc ()} " )
294
466
295
467
if self .lifespan is not None :
296
468
try :
@@ -569,12 +741,35 @@ async def on_startup_async():
569
741
570
742
# Use the appropriate event loop method based on Python version
571
743
try :
572
- asyncio .run (server .serve ())
744
+ # Wrap in try/except to handle server startup errors
745
+ try :
746
+ asyncio .run (server .serve ())
747
+ except AttributeError as e :
748
+ if "'Server' object has no attribute 'start'" in str (e ):
749
+ # If we get the 'start' attribute error, use our SimpleTCPServer directly
750
+ logger .warning ("Falling back to direct SimpleTCPServer implementation" )
751
+ direct_server = SimpleTCPServer (config )
752
+ asyncio .run (direct_server .serve ())
753
+ else :
754
+ raise
573
755
except RuntimeError as e :
574
756
# Handle "Event loop is already running" error
575
757
if "Event loop is already running" in str (e ):
576
758
logger .warning ("Event loop is already running. Using get_event_loop instead." )
577
- asyncio .get_event_loop ().run_until_complete (server .serve ())
759
+ loop = asyncio .get_event_loop ()
760
+ try :
761
+ loop .run_until_complete (server .serve ())
762
+ except AttributeError as e :
763
+ if "'Server' object has no attribute 'start'" in str (e ):
764
+ # If we get the 'start' attribute error, use our SimpleTCPServer directly
765
+ logger .warning ("Falling back to direct SimpleTCPServer implementation" )
766
+ direct_server = SimpleTCPServer (config )
767
+ loop .run_until_complete (direct_server .serve ())
768
+ else :
769
+ raise
770
+ else :
771
+ # Re-raise other errors
772
+ raise
578
773
else :
579
774
# Local environment
580
775
logger .info (f"Starting server on port { port } (local mode)" )
@@ -594,12 +789,32 @@ async def on_startup_async():
594
789
595
790
# Use asyncio.run which is more reliable
596
791
try :
597
- asyncio .run (server .serve ())
792
+ # Wrap in try/except to handle server startup errors
793
+ try :
794
+ asyncio .run (server .serve ())
795
+ except AttributeError as e :
796
+ if "'Server' object has no attribute 'start'" in str (e ):
797
+ # If we get the 'start' attribute error, use our SimpleTCPServer directly
798
+ logger .warning ("Falling back to direct SimpleTCPServer implementation" )
799
+ direct_server = SimpleTCPServer (config )
800
+ asyncio .run (direct_server .serve ())
801
+ else :
802
+ raise
598
803
except RuntimeError as e :
599
804
# Handle "Event loop is already running" error
600
805
if "Event loop is already running" in str (e ):
601
806
logger .warning ("Event loop is already running. Using get_event_loop instead." )
602
- asyncio .get_event_loop ().run_until_complete (server .serve ())
807
+ loop = asyncio .get_event_loop ()
808
+ try :
809
+ loop .run_until_complete (server .serve ())
810
+ except AttributeError as e :
811
+ if "'Server' object has no attribute 'start'" in str (e ):
812
+ # If we get the 'start' attribute error, use our SimpleTCPServer directly
813
+ logger .warning ("Falling back to direct SimpleTCPServer implementation" )
814
+ direct_server = SimpleTCPServer (config )
815
+ loop .run_until_complete (direct_server .serve ())
816
+ else :
817
+ raise
603
818
else :
604
819
# Re-raise other errors
605
820
raise
0 commit comments