77import json
88import time
99import uuid
10+ from logging import Logger
1011from typing import Any
1112
1213from jupyter_server .auth import authorized
@@ -105,6 +106,26 @@ async def prepare(self):
105106 self ._document_save_delay ,
106107 )
107108
109+ def exception_logger (exception : Exception , log : Logger ) -> bool :
110+ """A function that catches any exceptions raised in the websocket
111+ server and logs them.
112+
113+ The protects the y-room's task group from cancelling
114+ anytime an exception is raised.
115+ """
116+ room_id = "unknown"
117+ if self .room .room_id :
118+ room_id = self .room .room_id
119+ log .error (
120+ f"Document Room Exception, (room_id={ room_id } : " ,
121+ exc_info = exception ,
122+ )
123+ return True
124+
125+ # Logging exceptions, instead of raising them here to ensure
126+ # that the y-rooms stay alive even after an exception is seen.
127+ self .room .exception_handler = exception_logger
128+
108129 else :
109130 # TransientRoom
110131 # it is a transient document (e.g. awareness)
@@ -203,9 +224,12 @@ async def open(self, room_id):
203224 self .log .error (f"File { file .path } not found.\n { e !r} " , exc_info = e )
204225 self .close (1004 , f"File { file .path } not found." )
205226 else :
206- self .log .error (f"Error initializing: { file .path } \n { e !r} " , exc_info = e )
227+ self .log .error (
228+ f"Error initializing: { file .path } \n { e !r} " , exc_info = e
229+ )
207230 self .close (
208- 1003 , f"Error initializing: { file .path } . You need to close the document."
231+ 1003 ,
232+ f"Error initializing: { file .path } . You need to close the document." ,
209233 )
210234
211235 # Clean up the room and delete the file loader
@@ -272,16 +296,24 @@ async def on_message(self, message):
272296
273297 user = self .current_user
274298 data = json .dumps (
275- {"sender" : user .username , "timestamp" : time .time (), "content" : json .loads (msg )}
299+ {
300+ "sender" : user .username ,
301+ "timestamp" : time .time (),
302+ "content" : json .loads (msg ),
303+ }
276304 ).encode ("utf8" )
277305
278306 for client in self .room .clients :
279307 if client != self :
280308 task = asyncio .create_task (
281- client .send (bytes ([MessageType .CHAT ]) + write_var_uint (len (data )) + data )
309+ client .send (
310+ bytes ([MessageType .CHAT ]) + write_var_uint (len (data )) + data
311+ )
282312 )
283313 self ._websocket_server .background_tasks .add (task )
284- task .add_done_callback (self ._websocket_server .background_tasks .discard )
314+ task .add_done_callback (
315+ self ._websocket_server .background_tasks .discard
316+ )
285317
286318 self ._message_queue .put_nowait (message )
287319 self ._websocket_server .ypatch_nb += 1
@@ -300,7 +332,9 @@ def on_close(self) -> None:
300332 if self ._room_id != "JupyterLab:globalAwareness" :
301333 self ._emit_awareness_event (self .current_user .username , "leave" )
302334
303- def _emit (self , level : LogLevel , action : str | None = None , msg : str | None = None ) -> None :
335+ def _emit (
336+ self , level : LogLevel , action : str | None = None , msg : str | None = None
337+ ) -> None :
304338 _ , _ , file_id = decode_file_path (self ._room_id )
305339 path = self ._file_id_manager .get_path (file_id )
306340
@@ -312,12 +346,16 @@ def _emit(self, level: LogLevel, action: str | None = None, msg: str | None = No
312346
313347 self .event_logger .emit (schema_id = JUPYTER_COLLABORATION_EVENTS_URI , data = data )
314348
315- def _emit_awareness_event (self , username : str , action : str , msg : str | None = None ) -> None :
349+ def _emit_awareness_event (
350+ self , username : str , action : str , msg : str | None = None
351+ ) -> None :
316352 data = {"roomid" : self ._room_id , "username" : username , "action" : action }
317353 if msg :
318354 data ["msg" ] = msg
319355
320- self .event_logger .emit (schema_id = JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI , data = data )
356+ self .event_logger .emit (
357+ schema_id = JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI , data = data
358+ )
321359
322360 async def _clean_room (self ) -> None :
323361 """
@@ -387,7 +425,12 @@ async def put(self, path):
387425 # index already exists
388426 self .log .info ("Request for Y document '%s' with room ID: %s" , path , idx )
389427 data = json .dumps (
390- {"format" : format , "type" : content_type , "fileId" : idx , "sessionId" : SERVER_SESSION }
428+ {
429+ "format" : format ,
430+ "type" : content_type ,
431+ "fileId" : idx ,
432+ "sessionId" : SERVER_SESSION ,
433+ }
391434 )
392435 self .set_status (200 )
393436 return self .finish (data )
@@ -401,7 +444,12 @@ async def put(self, path):
401444 # index successfully created
402445 self .log .info ("Request for Y document '%s' with room ID: %s" , path , idx )
403446 data = json .dumps (
404- {"format" : format , "type" : content_type , "fileId" : idx , "sessionId" : SERVER_SESSION }
447+ {
448+ "format" : format ,
449+ "type" : content_type ,
450+ "fileId" : idx ,
451+ "sessionId" : SERVER_SESSION ,
452+ }
405453 )
406454 self .set_status (201 )
407455 return self .finish (data )
0 commit comments