diff --git a/jupyter_collaboration/loaders.py b/jupyter_collaboration/loaders.py index b84ce6b5..e85016d2 100644 --- a/jupyter_collaboration/loaders.py +++ b/jupyter_collaboration/loaders.py @@ -149,7 +149,7 @@ async def _watch_file(self) -> None: await self._maybe_load_document() except Exception as e: - self._log.error("Error watching file: %s\n", self.path, e) + self._log.error("Error watching file: %s\n%s", self.path, e) async def _maybe_load_document(self) -> None: """ diff --git a/jupyter_collaboration/rooms.py b/jupyter_collaboration/rooms.py index 57d59b20..a950ae5f 100644 --- a/jupyter_collaboration/rooms.py +++ b/jupyter_collaboration/rooms.py @@ -13,25 +13,6 @@ YFILE = YDOCS["file"] -class UpdateFlag: - """ - A context manager for ignoring self updates in the document. - """ - - def __init__(self): - self._updating = False - - def __enter__(self): - self._updating = True - - def __exit__(self, exc_type, exc_val, exc_tb): - self._updating = False - - @property - def updating(self) -> bool: - return self._updating - - class DocumentRoom(YRoom): """A Y room for a possibly stored document (e.g. a notebook).""" @@ -56,8 +37,8 @@ def __init__( self._save_delay = save_delay - self._lock = asyncio.Lock() - self._flag = UpdateFlag() + self._update_lock = asyncio.Lock() + self._initialization_lock = asyncio.Lock() self._cleaner: asyncio.Task | None = None self._saving_document: asyncio.Task | None = None @@ -101,14 +82,14 @@ async def initialize(self) -> None: It is important to set the ready property in the parent class (`self.ready = True`), this setter will subscribe for updates on the shared document. """ - async with self._lock: + async with self._initialization_lock: if self.ready: # type: ignore[has-type] return self.log.info("Initializing room %s", self._room_id) model = await self._file.load_content(self._file_format, self._file_type, True) - with self._flag: + async with self._update_lock: # try to apply Y updates from the YStore for this document read_from_source = True if self.ystore is not None: @@ -127,6 +108,7 @@ async def initialize(self) -> None: if not read_from_source: # if YStore updates and source file are out-of-sync, resync updates with source if self._document.source != model["content"]: + # TODO: Delete document from the store. self.log.info( "Content in file %s is out-of-sync with the ystore %s", self._file.path, @@ -174,7 +156,7 @@ async def _on_content_change(self, event: str, args: dict[str, Any]) -> None: self.log.info("Out-of-band changes. Overwriting the content in room %s", self._room_id) - with self._flag: + async with self._update_lock: self._document.source = model["content"] self._last_modified = model["last_modified"] self._document.dirty = False @@ -194,7 +176,7 @@ def _on_document_change(self, target: str, event: Any) -> None: document. This tasks are debounced (60 seconds by default) so we need to cancel previous tasks before creating a new one. """ - if self._flag.updating: + if self._update_lock.locked(): return if self._saving_document is not None and not self._saving_document.done(): @@ -230,13 +212,13 @@ async def _maybe_save_document(self) -> None: } ) self._last_modified = model["last_modified"] - with self._flag: + async with self._update_lock: self._document.dirty = False except OutOfBandChanges: self.log.info("Out-of-band changes. Overwriting the content in room %s", self._room_id) model = await self._file.load_content(self._file_format, self._file_type, True) - with self._flag: + async with self._update_lock: self._document.source = model["content"] self._last_modified = model["last_modified"] self._document.dirty = False diff --git a/pyproject.toml b/pyproject.toml index dc80d702..ff42d474 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ ] dependencies = [ "jupyter_server>=2.0.0,<3.0.0", - "jupyter_ydoc>=1.0.0,<2.0.0", + "jupyter_ydoc>=1.0.1,<2.0.0", "ypy-websocket>=0.8.3,<0.9.0", "jupyter_server_fileid>=0.6.0,<1" ]