Skip to content

Commit 7782f7a

Browse files
committed
always delete ydoc on disconnect
1 parent d23e9f7 commit 7782f7a

File tree

4 files changed

+52
-39
lines changed

4 files changed

+52
-39
lines changed

jupyter_rtc_core/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class RtcExtensionApp(ExtensionApp):
1919
# global awareness websocket
2020
# (r"api/collaboration/room/JupyterLab:globalAwareness/?", GlobalAwarenessWebsocket),
2121
# # ydoc websocket
22-
(r"api/collaboration/room/(.*)", YRoomWebsocket),
22+
(r"api/collaboration/room/(.*)/?", YRoomWebsocket),
2323
# # handler that just adds compatibility with Jupyter Collaboration's frontend
2424
# (r"api/collaboration/session/(.*)", YRoomSessionHandler),
2525
(r"api/fileid/index", FileIDIndexHandler)

jupyter_rtc_core/websockets/yroom_ws.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ class YRoomWebsocket(WebSocketHandler):
1515
yroom: YRoom
1616
room_id: str
1717
client_id: str
18-
# TODO: change this. we should pass `self.log` from our
19-
# `ExtensionApp` to log messages w/ "RtcCoreExtension" prefix
20-
log = logging.Logger("TEMP")
2118

2219

2320
@property
2421
def yroom_manager(self) -> YRoomManager:
2522
return self.settings["yroom_manager"]
23+
24+
25+
@property
26+
def log(self) -> logging.Logger:
27+
# TODO: change this because it's kind of a hack. we should probably set
28+
# up some way to reference our logger from `self.settings`
29+
return self.yroom_manager.log
2630

2731

2832
@property
@@ -35,33 +39,28 @@ def contents_manager(self) -> AsyncContentsManager | ContentsManager:
3539
return self.settings["contents_manager"]
3640

3741

38-
def prepare(self):
39-
# Bind `room_id` attribute
40-
request_path: str = self.request.path
41-
self.room_id = request_path.strip("/").split("/")[-1]
42+
def open(self, room_id, *_, **__):
43+
# Bind `room_id` as an instance attribute
44+
self.room_id = room_id
4245

43-
# TODO: remove this once globalawareness is implemented
44-
if self.room_id == "JupyterLab:globalAwareness":
46+
# TODO: remove this later
47+
if room_id == "JupyterLab:globalAwareness":
4548
self.close(1011)
4649
return
50+
51+
self.log.info(f"ROOM ID: {room_id}")
52+
self.log.info(f"REQUEST PATH: {self.request.path}")
4753

4854
# Verify the file ID contained in the room ID points to a valid file.
49-
fileid = self.room_id.split(":")[-1]
55+
fileid = room_id.split(":")[-1]
5056
path = self.fileid_manager.get_path(fileid)
5157
if not path:
5258
raise HTTPError(404, f"No file with ID '{fileid}'.")
53-
54-
55-
def open(self, *_, **__):
56-
# TODO: remove this later
57-
if self.room_id == "JupyterLab:globalAwareness":
58-
self.close(1011)
59-
return
6059

6160
# Create the YRoom
62-
yroom = self.yroom_manager.get_room(self.room_id)
61+
yroom = self.yroom_manager.get_room(room_id)
6362
if not yroom:
64-
raise HTTPError(500, f"Unable to initialize YRoom '{self.room_id}'.")
63+
raise HTTPError(500, f"Unable to initialize YRoom '{room_id}'.")
6564
self.yroom = yroom
6665

6766
# Add self as a client to the YRoom
@@ -71,6 +70,7 @@ def open(self, *_, **__):
7170
def on_message(self, message: bytes):
7271
# TODO: remove this later
7372
if self.room_id == "JupyterLab:globalAwareness":
73+
self.close(1011)
7474
return
7575

7676
# Route all messages to the YRoom for processing
@@ -82,5 +82,7 @@ def on_close(self):
8282
if self.room_id == "JupyterLab:globalAwareness":
8383
return
8484

85-
self.log.info(f"Closed Websocket to client '{self.client_id}'.")
85+
self.log.info(self.close_code)
86+
self.log.info(self.close_reason)
87+
self.log.info(f"Closed Websocket to client '{self.client_id}' on room '{self.room_id}'.")
8688
self.yroom.clients.remove(self.client_id)

src/docprovider/ydrive.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ export class RtcContentProvider
143143
if (typeof options.format !== 'string') {
144144
return;
145145
}
146-
147146

148147
try {
149148
const provider = new WebSocketProvider({

src/docprovider/yprovider.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,10 @@ export class WebSocketProvider implements IDocumentProvider {
9494

9595
private async _connect(): Promise<void> {
9696
// Fetch file ID from the file ID service.
97-
let resp = await requestAPI(
98-
"api/fileid/index?path=" + this._path,
99-
{
100-
method: "POST"
101-
}
102-
)
103-
const fileId = resp["id"];
97+
const resp = await requestAPI(`api/fileid/index?path=${this._path}`, {
98+
method: 'POST'
99+
});
100+
const fileId: string = resp['id'];
104101

105102
this._yWebsocketProvider = new YWebsocketProvider(
106103
this._serverUrl,
@@ -131,18 +128,33 @@ export class WebSocketProvider implements IDocumentProvider {
131128
this._awareness.setLocalStateField('user', user.identity);
132129
}
133130

131+
/**
132+
* Handles disconnections from the YRoom Websocket.
133+
*
134+
* TODO: handle disconnections more gracefully by reseting the YDoc to an
135+
* empty state on disconnect. Unfortunately the shared model does not provide
136+
* any methods for this, so we are just asking disconnected clients to
137+
* refresh for now.
138+
*
139+
* TODO: distinguish between disconnects when server YDoc history is the same
140+
* (i.e. SS1 + SS2 is sufficient), and when the history
141+
* differs (client's YDoc has to be reset before SS1 + SS2).
142+
*/
134143
private _onConnectionClosed = (event: any): void => {
135-
if (event.code === 1003) {
136-
console.error('Document provider closed:', event.reason);
137-
138-
showErrorMessage(this._trans.__('Document session error'), event.reason, [
139-
Dialog.okButton()
140-
]);
144+
// Log error to console for debugging
145+
console.error('WebSocket connection was closed. Close event: ', event);
146+
147+
// Show dialog to tell user to refresh the page
148+
showErrorMessage(
149+
this._trans.__('Document session error'),
150+
'Please refresh the browser tab.',
151+
[Dialog.okButton()]
152+
);
141153

142-
// Dispose shared model immediately. Better break the document model,
143-
// than overriding data on disk.
144-
this._sharedModel.dispose();
145-
}
154+
// Delete this client's YDoc by disposing of the shared model.
155+
// This is the only way we know of to stop `y-websocket` from constantly
156+
// attempting to re-connect.
157+
this._sharedModel.dispose();
146158
};
147159

148160
private _onSync = (isSynced: boolean) => {

0 commit comments

Comments
 (0)