1212
1313class YRoomManager ():
1414 """
15- A singleton that manages all `YRoom` instances in the server extension. This
16- automatically stops `YRoom` instances if they have had no connected clients
17- or active kernel for >10 seconds.
15+ A singleton that manages all `YRoom` instances in the server extension.
16+
17+ This manager automatically restarts updated `YRoom` instances if they have
18+ had no connected clients or active kernel for >10 seconds. This deletes the
19+ YDoc history to free its memory to the server.
1820 """
1921
2022 _rooms_by_id : dict [str , YRoom ]
2123 """
2224 Dictionary of active `YRoom` instances, keyed by room ID. Rooms are never
23- deleted from this dictionary, even if stopped due to inactivity .
25+ deleted from this dictionary.
2426
2527 TODO: Delete a room if its file was deleted in/out-of-band or moved
2628 out-of-band. See #116.
@@ -30,8 +32,7 @@ class YRoomManager():
3032 """
3133 Set of room IDs that were marked inactive on the last iteration of
3234 `_watch_rooms()`. If a room is inactive and its ID is present in this set,
33- then the room the room should be stopped as it has been inactive for >10
34- seconds.
35+ then the room should be restarted as it has been inactive for >10 seconds.
3536 """
3637
3738 _get_fileid_manager : callable [[], BaseFileIdManager ]
@@ -79,8 +80,7 @@ def get_room(self, room_id: str) -> YRoom | None:
7980 not exist, this method will initialize one and return it. Otherwise,
8081 this method returns the instance from its cache.
8182 """
82- # First, ensure this room stays open for >10 seconds by removing it from
83- # the inactive set of rooms if it is present.
83+ # First, ensure the room is not considered inactive.
8484 self ._inactive_rooms .discard (room_id )
8585
8686 # If room exists, return the room
@@ -144,14 +144,16 @@ def delete_room(self, room_id: str) -> None:
144144 async def _watch_rooms (self ) -> None :
145145 """
146146 Background task that checks all `YRoom` instances every 10 seconds,
147- stopping any rooms that have been inactive for >10 seconds.
147+ restarting any updated rooms that have been inactive for >10 seconds.
148+ This frees the memory occupied by the room's YDoc history, discarding it
149+ in the process.
148150
149- - For rooms providing notebooks: This task stops the room if it has no
150- connected clients and its kernel execution status is either 'idle' or
151- 'dead'.
151+ - For rooms providing notebooks: This task restarts the room if it has
152+ been updated, has no connected clients, and its kernel execution status
153+ is either 'idle' or 'dead'.
152154
153- - For all other rooms: This task stops the room if it has no connected
154- clients.
155+ - For all other rooms: This task restarts the room if it has been
156+ updated and has no connected clients.
155157 """
156158 while True :
157159 # Check every 10 seconds
@@ -161,7 +163,7 @@ async def _watch_rooms(self) -> None:
161163 room_ids = set (self ._rooms_by_id .keys ())
162164 room_ids .discard ("JupyterLab:globalAwareness" )
163165
164- # Iterate through all rooms. If any rooms are empty, stop the room .
166+ # Check all rooms and restart it if inactive for >10 seconds .
165167 for room_id in room_ids :
166168 self ._check_room (room_id )
167169
@@ -170,12 +172,15 @@ def _check_room(self, room_id: str) -> None:
170172 """
171173 Checks a room for inactivity.
172174
175+ - Rooms that have not been updated are not restarted, as there is no
176+ YDoc history to free.
177+
173178 - If a room is inactive and not in `_inactive_rooms`, this method adds
174179 the room to `_inactive_rooms`.
175180
176181 - If a room is inactive and is listed in `_inactive_rooms`, this method
177- stops the room, as it has been inactive for 2 consecutive iterations of
178- `_watch_rooms()`.
182+ restarts the room, as it has been inactive for 2 consecutive iterations
183+ of `_watch_rooms()`.
179184 """
180185 # Do nothing if the room has any connected clients.
181186 room = self ._rooms_by_id [room_id ]
@@ -192,15 +197,22 @@ def _check_room(self, room_id: str) -> None:
192197 if execution_state not in { "idle" , "dead" , None }:
193198 self ._inactive_rooms .discard (room_id )
194199 return
200+
201+ # Do nothing if the room has not been updated. This prevents empty rooms
202+ # from being restarted every 10 seconds.
203+ if not room .updated :
204+ self ._inactive_rooms .discard (room_id )
205+ return
195206
196- # The room is inactive if this line is reached.
197- # Stop the room if was marked as inactive in the last iteration,
207+ # The room is updated (with history) & inactive if this line is reached.
208+ # Restart the room if was marked as inactive in the last iteration,
198209 # otherwise mark it as inactive.
199210 if room_id in self ._inactive_rooms :
200211 self .log .info (
201- f"YRoom '{ room_id } ' has been inactive for >10 seconds. "
212+ f"Room '{ room_id } ' has been inactive for >10 seconds. "
213+ "Restarting the room to free memory occupied by its history."
202214 )
203- room .stop ()
215+ room .restart ()
204216 self ._inactive_rooms .discard (room_id )
205217 else :
206218 self ._inactive_rooms .add (room_id )
0 commit comments