diff --git a/dandi/misctypes.py b/dandi/misctypes.py index 9ed2dcf07..b50cffc42 100644 --- a/dandi/misctypes.py +++ b/dandi/misctypes.py @@ -345,10 +345,27 @@ def open(self) -> IO[bytes]: # Optional dependency: import fsspec + from aiohttp import ClientTimeout + # We need to call open() on the return value of fsspec.open() because # otherwise the filehandle will only be opened when used to enter a # context manager. - return cast(IO[bytes], fsspec.open(self.url, mode="rb").open()) + # + # Pass explicit timeouts to aiohttp to prevent indefinite hangs in + # fsspec's sync() wrapper. Without these, a stalled connection to S3 + # (or minio in tests) causes fsspec's background IO thread to block + # forever, which in turn blocks the calling thread in + # threading.Event.wait() — see https://github.com/fsspec/filesystem_spec/issues/1666 + return cast( + IO[bytes], + fsspec.open( + self.url, + mode="rb", + client_kwargs={ + "timeout": ClientTimeout(total=120, sock_read=60, sock_connect=30) + }, + ).open(), + ) def get_size(self) -> int: return self.size diff --git a/dandi/tests/test_metadata.py b/dandi/tests/test_metadata.py index 1170e67af..1d0b8697e 100644 --- a/dandi/tests/test_metadata.py +++ b/dandi/tests/test_metadata.py @@ -1145,6 +1145,7 @@ def test_nwb2asset(simple2_nwb: Path) -> None: ) +@pytest.mark.timeout(120) @pytest.mark.xfail(reason="https://github.com/dandi/dandi-cli/issues/1450") def test_nwb2asset_remote_asset(nwb_dandiset: SampleDandiset) -> None: pytest.importorskip("fsspec")