Skip to content

Commit 527001c

Browse files
committed
ensure returned rooms are kept alive for 10 seconds
1 parent 4fb34b8 commit 527001c

File tree

1 file changed

+33
-4
lines changed

1 file changed

+33
-4
lines changed

jupyter_server_documents/rooms/yroom_manager.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ class YRoomManager():
3030
not currently stopping. This is ensured by `_handle_yroom_stopping()`.
3131
"""
3232

33+
_inactive_rooms: set[str]
34+
"""
35+
Set of room IDs that were marked inactive on the last iteration of
36+
`_watch_rooms()`. If a room is inactive and its ID is present in this set,
37+
then the room has been inactive for >10 seconds, and the room should be
38+
deleted in `_watch_rooms()`.
39+
"""
40+
3341
_get_fileid_manager: callable[[], BaseFileIdManager]
3442
contents_manager: AsyncContentsManager | ContentsManager
3543
event_logger: EventLogger
@@ -56,6 +64,9 @@ def __init__(
5664
# Initialize dictionary of YRooms, keyed by room ID
5765
self._rooms_by_id = {}
5866

67+
# Initialize set of inactive rooms tracked by `self._watch_rooms()`
68+
self._inactive_rooms = set()
69+
5970
# Start `self._watch_rooms()` background task to automatically stop
6071
# empty rooms
6172
self._watch_rooms_task = self.loop.create_task(self._watch_rooms())
@@ -70,7 +81,14 @@ def get_room(self, room_id: str) -> YRoom | None:
7081
"""
7182
Retrieves a YRoom given a room ID. If the YRoom does not exist, this
7283
method will initialize a new YRoom.
84+
85+
This method ensures that the returned room will be alive for >10
86+
seconds. This prevents the room from being deleted shortly after the
87+
consumer receives it via this method, even if it is inactive.
7388
"""
89+
# First, ensure this room stays open for >10 seconds by removing it from
90+
# the inactive set of rooms if it is present.
91+
self._inactive_rooms.discard(room_id)
7492

7593
# If room exists, return the room
7694
if room_id in self._rooms_by_id:
@@ -142,7 +160,7 @@ async def delete_room(self, room_id: str) -> None:
142160
async def _watch_rooms(self) -> None:
143161
"""
144162
Background task that checks all `YRoom` instances every 10 seconds,
145-
deleting any rooms that are totally inactive.
163+
deleting any rooms that have been inactive for >10 seconds.
146164
147165
- For rooms providing notebooks: This task deletes the room if it has no
148166
connected clients and its kernel execution status is either 'idle' or
@@ -169,11 +187,13 @@ async def _watch_rooms(self) -> None:
169187
# happens if the room was stopped by something else while this
170188
# `for` loop is still running, so we must check.
171189
if room_id not in self._rooms_by_id:
190+
self._inactive_rooms.discard(room_id)
172191
continue
173192

174193
# Continue if the room has any connected clients.
175194
room = self._rooms_by_id[room_id]
176195
if room.clients.count != 0:
196+
self._inactive_rooms.discard(room_id)
177197
continue
178198

179199
# Continue if the room contains a notebook with kernel execution
@@ -183,11 +203,20 @@ async def _watch_rooms(self) -> None:
183203
awareness = room.get_awareness().get_local_state() or {}
184204
execution_state = awareness.get("kernel", {}).get("execution_state", None)
185205
if execution_state not in { "idle", "dead", None }:
206+
self._inactive_rooms.discard(room_id)
186207
continue
187208

188-
# Otherwise, delete the room
189-
self.log.info(f"Found empty YRoom '{room_id}'.")
190-
self.loop.create_task(self.delete_room(room_id))
209+
# The room is inactive if this statement is reached
210+
# Delete the room if was marked as inactive in the last
211+
# iteration, otherwise mark it as inactive.
212+
if room_id in self._inactive_rooms:
213+
self.log.info(
214+
f"YRoom '{room_id}' has been inactive for >10 seconds. "
215+
)
216+
self.loop.create_task(self.delete_room(room_id))
217+
self._inactive_rooms.discard(room_id)
218+
else:
219+
self._inactive_rooms.add(room_id)
191220

192221

193222
async def stop(self) -> None:

0 commit comments

Comments
 (0)