@@ -57,6 +57,12 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
57
57
_serve_task : asyncio .Task | None
58
58
_message_queue : asyncio .Queue [Any ]
59
59
_background_tasks : set [asyncio .Task ]
60
+ _room_locks : dict [str , asyncio .Lock ] = {}
61
+
62
+ def _room_lock (self , room_id : str ) -> asyncio .Lock :
63
+ if room_id not in self ._room_locks :
64
+ self ._room_locks [room_id ] = asyncio .Lock ()
65
+ return self ._room_locks [room_id ]
60
66
61
67
def create_task (self , aw ):
62
68
task = asyncio .create_task (aw )
@@ -71,38 +77,38 @@ async def prepare(self):
71
77
# Get room
72
78
self ._room_id : str = self .request .path .split ("/" )[- 1 ]
73
79
74
- if self ._websocket_server .room_exists (self ._room_id ):
75
- self .room : YRoom = await self ._websocket_server .get_room (self ._room_id )
76
-
77
- else :
78
- if self ._room_id .count (":" ) >= 2 :
79
- # DocumentRoom
80
- file_format , file_type , file_id = decode_file_path (self ._room_id )
81
- if file_id in self ._file_loaders :
82
- self ._emit (
83
- LogLevel .WARNING ,
84
- None ,
85
- "There is another collaborative session accessing the same file.\n The synchronization between rooms is not supported and you might lose some of your changes." ,
80
+ async with self ._room_lock (self ._room_id ):
81
+ if self ._websocket_server .room_exists (self ._room_id ):
82
+ self .room : YRoom = await self ._websocket_server .get_room (self ._room_id )
83
+ else :
84
+ if self ._room_id .count (":" ) >= 2 :
85
+ # DocumentRoom
86
+ file_format , file_type , file_id = decode_file_path (self ._room_id )
87
+ if file_id in self ._file_loaders :
88
+ self ._emit (
89
+ LogLevel .WARNING ,
90
+ None ,
91
+ "There is another collaborative session accessing the same file.\n The synchronization between rooms is not supported and you might lose some of your changes." ,
92
+ )
93
+
94
+ file = self ._file_loaders [file_id ]
95
+ updates_file_path = f".{ file_type } :{ file_id } .y"
96
+ ystore = self ._ystore_class (path = updates_file_path , log = self .log )
97
+ self .room = DocumentRoom (
98
+ self ._room_id ,
99
+ file_format ,
100
+ file_type ,
101
+ file ,
102
+ self .event_logger ,
103
+ ystore ,
104
+ self .log ,
105
+ self ._document_save_delay ,
86
106
)
87
107
88
- file = self ._file_loaders [file_id ]
89
- updates_file_path = f".{ file_type } :{ file_id } .y"
90
- ystore = self ._ystore_class (path = updates_file_path , log = self .log )
91
- self .room = DocumentRoom (
92
- self ._room_id ,
93
- file_format ,
94
- file_type ,
95
- file ,
96
- self .event_logger ,
97
- ystore ,
98
- self .log ,
99
- self ._document_save_delay ,
100
- )
101
-
102
- else :
103
- # TransientRoom
104
- # it is a transient document (e.g. awareness)
105
- self .room = TransientRoom (self ._room_id , self .log )
108
+ else :
109
+ # TransientRoom
110
+ # it is a transient document (e.g. awareness)
111
+ self .room = TransientRoom (self ._room_id , self .log )
106
112
107
113
await self ._websocket_server .start_room (self .room )
108
114
self ._websocket_server .add_room (self ._room_id , self .room )
@@ -191,7 +197,8 @@ async def open(self, room_id):
191
197
192
198
try :
193
199
# Initialize the room
194
- await self .room .initialize ()
200
+ async with self ._room_lock (self ._room_id ):
201
+ await self .room .initialize ()
195
202
self ._emit_awareness_event (self .current_user .username , "join" )
196
203
except Exception as e :
197
204
_ , _ , file_id = decode_file_path (self ._room_id )
@@ -319,29 +326,31 @@ async def _clean_room(self) -> None:
319
326
contains a copy of the document. In addition, we remove the file if there is no rooms
320
327
subscribed to it.
321
328
"""
322
- assert isinstance (self .room , DocumentRoom )
329
+ async with self ._room_lock (self ._room_id ):
330
+ assert isinstance (self .room , DocumentRoom )
323
331
324
- if self ._cleanup_delay is None :
325
- return
332
+ if self ._cleanup_delay is None :
333
+ return
326
334
327
- await asyncio .sleep (self ._cleanup_delay )
335
+ await asyncio .sleep (self ._cleanup_delay )
328
336
329
- # Remove the room from the websocket server
330
- self .log .info ("Deleting Y document from memory: %s" , self .room .room_id )
331
- self ._websocket_server .delete_room (room = self .room )
337
+ # Remove the room from the websocket server
338
+ self .log .info ("Deleting Y document from memory: %s" , self .room .room_id )
339
+ self ._websocket_server .delete_room (room = self .room )
332
340
333
- # Clean room
334
- del self .room
335
- self .log .info ("Room %s deleted" , self ._room_id )
336
- self ._emit (LogLevel .INFO , "clean" , "Room deleted." )
341
+ # Clean room
342
+ del self .room
343
+ self .log .info ("Room %s deleted" , self ._room_id )
344
+ self ._emit (LogLevel .INFO , "clean" , "Room deleted." )
337
345
338
- # Clean the file loader if there are not rooms using it
339
- _ , _ , file_id = decode_file_path (self ._room_id )
340
- file = self ._file_loaders [file_id ]
341
- if file .number_of_subscriptions == 0 :
342
- self .log .info ("Deleting file %s" , file .path )
343
- await self ._file_loaders .remove (file_id )
344
- self ._emit (LogLevel .INFO , "clean" , "Loader deleted." )
346
+ # Clean the file loader if there are not rooms using it
347
+ _ , _ , file_id = decode_file_path (self ._room_id )
348
+ file = self ._file_loaders [file_id ]
349
+ if file .number_of_subscriptions == 0 :
350
+ self .log .info ("Deleting file %s" , file .path )
351
+ await self ._file_loaders .remove (file_id )
352
+ self ._emit (LogLevel .INFO , "clean" , "Loader deleted." )
353
+ del self ._room_locks [self ._room_id ]
345
354
346
355
def check_origin (self , origin ):
347
356
"""
0 commit comments