You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
async zarr.create_array fails second call through async.run() #2878
I have an async function that uses async zarr create_array to create an array in a S3 bucket using FspecStore. I call this function from a synchronous function using async.run(). The first time it works without problem. The second time create_array fails with this this error:
Error: An HTTP Client raised an unhandled exception: Event loop is closed
It seems that some ressource is still link to the loop created by the first call to async.run.
If you create your own loop and re-use it to run the async code, it works
Steps to reproduce
To run you need to set {you_own_bucket}to a S3 bucket
import asyncio
import numpy as np
import s3fs
from zarr.api.asynchronous import create_array
from zarr.storage import FsspecStore
_loop = None
def run_async_function(async_func, *args, **kwargs):
global _loop
if _loop is None or _loop.is_closed():
_loop = asyncio.new_event_loop()
asyncio.set_event_loop(_loop)
return _loop.run_until_complete(async_func(*args, **kwargs))
async def test_zarr_async():
s3 = s3fs.S3FileSystem(asynchronous=True)
s3_path = "s3://{your_own_bucket}/test.zarr"
store = FsspecStore(fs=s3, path= s3_path[4:])
await create_array(store, shape=(10, 10), dtype=np.float64, chunks=(5, 5), overwrite=True)
store.close()
# First time works
asyncio.run(test_zarr_async())
# second time fails
try:
asyncio.run(test_zarr_async())
except Exception as e:
print(f"Error: {e}")
# running this instead of async.run() works, because we use the same loop
# on both call
'''
run_async_function(test_zarr_async)
run_async_function(test_zarr_async)
'''
Hi @rpitre-wetdog - thanks for the report. I was able to reproduce this with a few minor tweaks. In general, I would say that for best results (performance, reliability, etc), I would use just one loop per program. Your work around seems to be directionally aligned.
I'll note that I can't reproduce the error with any other stores (MemoryStore or LocalStore) so this seems to be confined to Fsspec. Presumably this has to do with a client cache managed in fsspec or s3fs. @martindurant will probably know.
Sorry, I forgot to answer here. Details of the fsspec instance caching strategy:
Instances are keyed by the set of kwargs passed in at instantiation, plus thread ID, PID and optional extra arguments (the latter depending on implementation class).
For async implementations, the the loop is an important attribute of the instance. It will be the "current" loop when instantiated in an async context, but the IOloop in fsspec's own thread if not. This is really why the flag asynchronous= exists, to make sure that these instances do not end up clashing in the cache.
In the case described here, I think the instance is being created twice with identical kwargs in an async context, but with different loops. This is not an anticipated circumstance! So, one could
explicitly avoid the cache altogether,
include the loop= explicitly in creating the filesystem
try to find an automatic way that id(loop) is yet another part of the cache key for async instances created in async contexts
Activity
jhamman commentedon Mar 1, 2025
Hi @rpitre-wetdog - thanks for the report. I was able to reproduce this with a few minor tweaks. In general, I would say that for best results (performance, reliability, etc), I would use just one loop per program. Your work around seems to be directionally aligned.
I'll note that I can't reproduce the error with any other stores (
MemoryStoreorLocalStore) so this seems to be confined to Fsspec. Presumably this has to do with a client cache managed in fsspec or s3fs. @martindurant will probably know.The full traceback is here:
Details
</details?
martindurant commentedon Mar 13, 2025
Sorry, I forgot to answer here. Details of the fsspec instance caching strategy:
Instances are keyed by the set of kwargs passed in at instantiation, plus thread ID, PID and optional extra arguments (the latter depending on implementation class).
For async implementations, the the
loopis an important attribute of the instance. It will be the "current" loop when instantiated in an async context, but the IOloop in fsspec's own thread if not. This is really why the flag asynchronous= exists, to make sure that these instances do not end up clashing in the cache.In the case described here, I think the instance is being created twice with identical kwargs in an async context, but with different loops. This is not an anticipated circumstance! So, one could
id(loop)is yet another part of the cache key for async instances created in async contextsjhamman commentedon Mar 15, 2025
👍 This seems like the most ideal outcome from Zarr's perspective.
martindurant commentedon Mar 17, 2025
Repeating my proposed diff from the other issue: