diff --git a/src/zarr/storage/_zip.py b/src/zarr/storage/_zip.py index 72bf9e335a..b051b2cbc8 100644 --- a/src/zarr/storage/_zip.py +++ b/src/zarr/storage/_zip.py @@ -87,13 +87,12 @@ def __init__( self._zmode = mode self.compression = compression self.allowZip64 = allowZip64 + self._lock = threading.RLock() def _sync_open(self) -> None: if self._is_open: raise ValueError("store is already open") - self._lock = threading.RLock() - self._zf = zipfile.ZipFile( self.path, mode=self._zmode, diff --git a/tests/test_store/test_zip.py b/tests/test_store/test_zip.py index 744ee82945..11399fff77 100644 --- a/tests/test_store/test_zip.py +++ b/tests/test_store/test_zip.py @@ -152,3 +152,15 @@ async def test_move(self, tmp_path: Path) -> None: assert destination.exists() assert not origin.exists() assert np.array_equal(array[...], np.arange(10)) + + def test_lock_initialized_before_open(self, store_kwargs: dict[str, Any]) -> None: + """Regression test for https://github.com/zarr-developers/zarr-python/issues/3588. + + ZipStore._lock must be available immediately after __init__, before _open() is called, + so that operations like get/set/exists can use it even on non-opened stores. + """ + import threading + + store = self.store_cls(**store_kwargs) + assert hasattr(store, "_lock"), "ZipStore._lock must be initialized in __init__" + assert isinstance(store._lock, type(threading.RLock())), "_lock must be an RLock"