3
3
from typing import Union , Text , Optional , AnyStr , Dict , Any , Callable
4
4
import logging
5
5
from websocket_server import WebsocketServer
6
+ import socket
6
7
7
8
from . import blocks
8
9
from . import sources as src
@@ -223,8 +224,13 @@ def _on_message_received(
223
224
if client_id in self ._clients :
224
225
try :
225
226
self ._clients [client_id ].audio_source .process_message (message )
227
+ except (socket .error , ConnectionError ) as e :
228
+ logger .warning (f"Client { client_id } disconnected: { e } " )
229
+ self .close (client_id )
226
230
except Exception as e :
227
231
logger .error (f"Error processing message from client { client_id } : { e } " )
232
+ # Don't close the connection for non-connection related errors
233
+ # This allows the client to retry sending the message
228
234
229
235
def send (self , client_id : Text , message : AnyStr ) -> None :
230
236
"""Send a message to a specific client.
@@ -247,18 +253,34 @@ def send(self, client_id: Text, message: AnyStr) -> None:
247
253
if client is not None :
248
254
try :
249
255
self .server .send_message (client , message )
256
+ except (socket .error , ConnectionError ) as e :
257
+ logger .warning (f"Client { client_id } disconnected while sending message: { e } " )
258
+ self .close (client_id )
250
259
except Exception as e :
251
260
logger .error (f"Failed to send message to client { client_id } : { e } " )
252
261
253
262
def run (self ) -> None :
254
263
"""Start the WebSocket server."""
255
264
logger .info (f"Starting WebSocket server on { self .uri } " )
256
- try :
257
- self .server .run_forever ()
258
- except Exception as e :
259
- logger .error (f"Server error: { e } " )
260
- finally :
261
- self .close_all ()
265
+ max_retries = 3
266
+ retry_count = 0
267
+
268
+ while retry_count < max_retries :
269
+ try :
270
+ self .server .run_forever ()
271
+ break # If server exits normally, break the retry loop
272
+ except (socket .error , ConnectionError ) as e :
273
+ logger .warning (f"WebSocket connection error: { e } " )
274
+ retry_count += 1
275
+ if retry_count < max_retries :
276
+ logger .info (f"Attempting to restart server (attempt { retry_count + 1 } /{ max_retries } )" )
277
+ else :
278
+ logger .error ("Max retry attempts reached. Server shutting down." )
279
+ except Exception as e :
280
+ logger .error (f"Fatal server error: { e } " )
281
+ break
282
+ finally :
283
+ self .close_all ()
262
284
263
285
def close (self , client_id : Text ) -> None :
264
286
"""Close a specific client's connection and cleanup resources.
@@ -277,9 +299,20 @@ def close(self, client_id: Text) -> None:
277
299
# Close audio source and remove client
278
300
client_state .audio_source .close ()
279
301
del self ._clients [client_id ]
302
+
303
+ # Try to send a close frame to the client
304
+ try :
305
+ client = next ((c for c in self .server .clients if c ["id" ] == client_id ), None )
306
+ if client :
307
+ self .server .send_message (client , "CLOSE" )
308
+ except Exception :
309
+ pass # Ignore errors when trying to send close message
310
+
280
311
logger .info (f"Closed connection and cleaned up state for client: { client_id } " )
281
312
except Exception as e :
282
313
logger .error (f"Error closing client { client_id } : { e } " )
314
+ # Ensure client is removed from dictionary even if cleanup fails
315
+ self ._clients .pop (client_id , None )
283
316
284
317
def close_all (self ) -> None :
285
318
"""Shutdown the server and cleanup all client connections."""
0 commit comments