7
7
import json
8
8
import time
9
9
import uuid
10
+ from logging import Logger
10
11
from typing import Any
11
12
12
13
from jupyter_server .auth import authorized
@@ -105,6 +106,26 @@ async def prepare(self):
105
106
self ._document_save_delay ,
106
107
)
107
108
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
+
108
129
else :
109
130
# TransientRoom
110
131
# it is a transient document (e.g. awareness)
@@ -203,9 +224,12 @@ async def open(self, room_id):
203
224
self .log .error (f"File { file .path } not found.\n { e !r} " , exc_info = e )
204
225
self .close (1004 , f"File { file .path } not found." )
205
226
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
+ )
207
230
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." ,
209
233
)
210
234
211
235
# Clean up the room and delete the file loader
@@ -272,16 +296,24 @@ async def on_message(self, message):
272
296
273
297
user = self .current_user
274
298
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
+ }
276
304
).encode ("utf8" )
277
305
278
306
for client in self .room .clients :
279
307
if client != self :
280
308
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
+ )
282
312
)
283
313
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
+ )
285
317
286
318
self ._message_queue .put_nowait (message )
287
319
self ._websocket_server .ypatch_nb += 1
@@ -300,7 +332,9 @@ def on_close(self) -> None:
300
332
if self ._room_id != "JupyterLab:globalAwareness" :
301
333
self ._emit_awareness_event (self .current_user .username , "leave" )
302
334
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 :
304
338
_ , _ , file_id = decode_file_path (self ._room_id )
305
339
path = self ._file_id_manager .get_path (file_id )
306
340
@@ -312,12 +346,16 @@ def _emit(self, level: LogLevel, action: str | None = None, msg: str | None = No
312
346
313
347
self .event_logger .emit (schema_id = JUPYTER_COLLABORATION_EVENTS_URI , data = data )
314
348
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 :
316
352
data = {"roomid" : self ._room_id , "username" : username , "action" : action }
317
353
if msg :
318
354
data ["msg" ] = msg
319
355
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
+ )
321
359
322
360
async def _clean_room (self ) -> None :
323
361
"""
@@ -387,7 +425,12 @@ async def put(self, path):
387
425
# index already exists
388
426
self .log .info ("Request for Y document '%s' with room ID: %s" , path , idx )
389
427
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
+ }
391
434
)
392
435
self .set_status (200 )
393
436
return self .finish (data )
@@ -401,7 +444,12 @@ async def put(self, path):
401
444
# index successfully created
402
445
self .log .info ("Request for Y document '%s' with room ID: %s" , path , idx )
403
446
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
+ }
405
453
)
406
454
self .set_status (201 )
407
455
return self .finish (data )
0 commit comments