Problem
Opening a large text file (e.g. a 176MB log file) in JupyterLab with jupyter_server_ydoc installed causes the Jupyter server process to peg at 100% CPU indefinitely, making the server completely unresponsive. The server must be killed to recover, and the problem recurs on restart if the file tab is restored.
Root cause
YUnicode.set() in yunicode.py calls difflib.SequenceMatcher.ratio() to decide whether to perform a granular update or a full reload:
matcher = SequenceMatcher(a=before_bytes, b=after_bytes)
if (
matcher.real_quick_ratio() >= SIMILARITY_THREESHOLD
and matcher.ratio() >= SIMILARITY_THREESHOLD
):
SequenceMatcher.ratio() internally calls get_matching_blocks() → find_longest_match(), which is O(n * m) in the lengths of the two sequences. For a 176MB file this means diffing ~167M × 184M bytes, which takes hours or longer and blocks the main thread (with the GIL held) for the entire duration.
The real_quick_ratio() check is fast but doesn't help when the file is being loaded for the first time (old value is empty → ratio is 0.0 → but the file being opened as a new ydoc room goes through rooms.py:initialize → ybasedoc.py:aset → yunicode.py:set where the old value is already populated from ystore).
Stack trace
Captured with py-spy dump --pid <PID>:
Thread 7 (active+gil): "MainThread"
find_longest_match (difflib.py:385)
ahi: 167472901
bhi: 184489591
get_matching_blocks (difflib.py:454)
ratio (difflib.py:619)
set (jupyter_ydoc/yunicode.py:85)
aset (jupyter_ydoc/ybasedoc.py:203)
initialize (jupyter_server_ydoc/rooms.py:164)
open (jupyter_server_ydoc/handlers.py:278)
Impact
- Server becomes completely unresponsive (all tabs, all kernels, all terminals)
- The
.jupyter_ystore.db grows to match the file size (~177MB in our case)
- Restarting the server doesn't help if the browser restores the tab — the problem immediately recurs
- Recovery requires removing the file (or renaming it so the tab can't find it), deleting
.jupyter_ystore.db, and restarting
Suggested fix
Add a file size threshold before attempting difflib-based granular updates. For files above a certain size (e.g. 5MB or 10MB), skip the SequenceMatcher entirely and fall back to the full-reload path which is already implemented in the else branch:
MAX_DIFF_SIZE = 5 * 1024 * 1024 # 5 MB
if len(before_bytes) > MAX_DIFF_SIZE or len(after_bytes) > MAX_DIFF_SIZE:
self._ysource.clear()
if value:
self._ysource += value
else:
matcher = SequenceMatcher(a=before_bytes, b=after_bytes)
...
Environment
- jupyter_ydoc 3.1.1
- jupyter_server_ydoc 2.3.0
- Python 3.13.12
- JupyterLab 4.5.6
Problem
Opening a large text file (e.g. a 176MB log file) in JupyterLab with
jupyter_server_ydocinstalled causes the Jupyter server process to peg at 100% CPU indefinitely, making the server completely unresponsive. The server must be killed to recover, and the problem recurs on restart if the file tab is restored.Root cause
YUnicode.set()inyunicode.pycallsdifflib.SequenceMatcher.ratio()to decide whether to perform a granular update or a full reload:SequenceMatcher.ratio()internally callsget_matching_blocks()→find_longest_match(), which is O(n * m) in the lengths of the two sequences. For a 176MB file this means diffing ~167M × 184M bytes, which takes hours or longer and blocks the main thread (with the GIL held) for the entire duration.The
real_quick_ratio()check is fast but doesn't help when the file is being loaded for the first time (old value is empty → ratio is 0.0 → but the file being opened as a new ydoc room goes throughrooms.py:initialize→ybasedoc.py:aset→yunicode.py:setwhere the old value is already populated from ystore).Stack trace
Captured with
py-spy dump --pid <PID>:Impact
.jupyter_ystore.dbgrows to match the file size (~177MB in our case).jupyter_ystore.db, and restartingSuggested fix
Add a file size threshold before attempting difflib-based granular updates. For files above a certain size (e.g. 5MB or 10MB), skip the
SequenceMatcherentirely and fall back to the full-reload path which is already implemented in theelsebranch:Environment