Skip to content

Conversation

@dlqqq
Copy link
Collaborator

@dlqqq dlqqq commented May 6, 2025

Description

Provides a basic implementation of YRoom, as described in #3. The YRoom:

  • Initializes pycrdt.Doc() and pycrdt.Awareness() for a given file,
  • Receives all new messages from connected clients for 1 YDoc,
  • Uses a message queue to store messages while the file is being read from disk,
  • Starts a background async task that continuously polls the message queue,
  • Handles SyncStep1, SyncUpdate, and AwarenessUpdate messages using pycrdt, and
  • Produces highly detailed warnings & errors to make debugging easier.

This YRoom does not do anything yet because it still requires:

  • Some sort of file loader to read from & write to a file on disk using the Jupyter Server ContentsManager,
  • A YRoomManager to initialize the file loader, and
  • A YRoomWebsocket that calls methods on YRoomManager to start rooms when clients connect to one without other collaborators.

This PR also:

  • Adds jupyter_server_fileid as a dependency,
  • Adds empty interfaces for YjsClientGroup, YRoomManager, and YRoomLoader (name might change).
    • @jzhang20133 You can use the interface provided by this PR to implement YjsClientGroup.

Follow-up issues

  • TODO: new issue for file loader
  • TODO: new issue for YRoomManager
  • TODO: new issue for YRoomWebsocket
  • TODO: new issue for some kind of formatter like pre-commit. The code looks a bit ugly because of the long logging statements.

return

# Broadcast the SyncUpdate to all other synced clients
self._broadcast_message_from(client_id, message, message_type="SyncUpdate")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

broadcast messages to all clients could take a long time especially when there are many clients. It will make this call blocking and take a long time to finish and meanwhile, no other messages can be processed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In original implementation, there is a object stream buffer to hold those messages that need to be broadcasted and broadcasting in another task.

Copy link
Collaborator Author

@dlqqq dlqqq May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that tornado.websocket.WebSocketHandler.write_message() is synchronous. We could make this async by running this logic in a separate thread (e.g. via ThreadPoolExecutor), but I don't know if that will improve performance due to the Python GIL.

I think the best way to address this is to: 1) time this using logs for now, then 2) address this performance issue if it proves to be a concern. What do you think?

Copy link
Collaborator

@jzhang20133 jzhang20133 May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation used a object stream buffer to break up the operations in half and achieve async broadcasting leveraging a object stream/queue. We could definitely come back to this in future PRs.

@jzhang20133
Copy link
Collaborator

Do we usually squash commits into one for each PR? Or we use that pattern. I am OK either way.

Copy link
Collaborator

@jzhang20133 jzhang20133 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve to unblock and we can address issues in future PRs

@dlqqq
Copy link
Collaborator Author

dlqqq commented May 7, 2025

@jzhang20133 Yup, generally we squash all PRs before merging them to keep the commit history clean. Also makes backporting changes easier, which is important post-1.0.0.

@dlqqq
Copy link
Collaborator Author

dlqqq commented May 7, 2025

More feedback from Jialin (that can be addressed in a future PR):

  • Use a 2nd "update queue" that is added to whenever the ydoc observer fires. This allows us to guarantee that new updates are broadcast in order, and we don't have to rely on how the ydoc observer works in pycrdt.

  • Consider bringing back the YjsClientGroup.get_others() method (N-1) instead of always broadcasting to all N clients.

@dlqqq dlqqq merged commit 4d81264 into jupyter-ai-contrib:main May 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants