Skip to content

CachingFileManager opens one file handle per open_dataset() call — could identical managers share a FILE_CACHE entry? #11581

Open

Description

@djhoese

What is your issue?

Disclaimer: My understanding of Xarray internals provided by Claude.

I have a situation in my downstream library Satpy where code was written to open a NetCDF file with open_dataset multiple times. The files I was dealing with had a lot of groups and loading a dataset included ~40 files. The initial naive implementation called open_dataset once per variable and loaded things as dask arrays. Due to a bug in HDF5 which I've filed privately upstream this was causing a segmentation fault when xarray was closing one cached entry even though others for the same file still existed and were open.

CachingFileManager._make_key() includes self._manager_id, a fresh uuid4() per
manager. So N open_dataset() calls on the same path with identical kwargs produce N
independent handles, each taking its own FILE_CACHE slot. With the default
file_cache_maxsize=128 and our naive implementation we end up thrashing the LRU cache even for a single file. Since every open_dataset is a new entry that means the file is reopened and metadata is reparsed (at least that's what Claude says). Under the hood my understanding is that HDF5 is only keeping one actual open file in modern HDF5 versions.

I'm wondering whether that is intended in this case, or whether managers that are
simultaneously alive and compare equal on (opener, args, mode, kwargs) could share one
open file? I know there was #4879 to fix #4240 and #4862. I'm not saying that I disagree with that use case and those changes, but wondering if a further optimization can be done (even if opt-in) to share a single Dataset for the same combination. I think Claude said that even for group-specific open_dataset calls the root/top-level NetCDF4.Dataset object is held onto? So maybe sharing the entry for all groups would work too.

Claude suggested two possible directions that I'll summarize here:

  1. To keep the Cache files for different CachingFileManager objects separately #4879 fix working and prevent new open_dataset calls not reusing an old cache entry, make the caching smarter to see "does this cache entry have any active managers", reuse that entry if so or open a new version if no active managers.
  2. Make the LRU cache more understanding of entries that share the same paths and handle them differently. Like sizing/accounting per path. I don't see how this would be possible as the cache understanding that two or more entries are "equivalent" would probably mean they should just use the same entry in the first place.

Activity

  1. added
    needs triageIssue that has not been reviewed by xarray team member
    on Sep 11, 2026
  2. djhoese commented on Sep 11, 2026

    @djhoese
    ContributorAuthor

    Oh I should have noted that I've already started fixing our issue in Satpy by calling open_dataset once per group and holding on to the xarray Dataset for reuse.

  3. headtr1ck commented on Oct 12, 2026

    @headtr1ck
    Collaborator

    Thanks for the detailed write-up, and sorry for the slow reply. The separate cache entry per open_dataset() call is intentional: #4879 added the manager id to the key so that opening a file again gives you its current state, e.g. after it was modified on disk (#4862), instead of a cached handle that may still show the old content.

    For your case of one file with many groups, xr.open_groups(path) or xr.open_datatree(path) should already do what you want: they open the file only once and share that handle for all groups. With a test file with 10 groups, open_dataset(..., group=...) per group opens the file 10 times, while open_groups/open_datatree open it once.

    The segfault you hit is most likely HDFGroup/hdf5#6721 (case 2): with several handles to the same file, closing one of them frees state that the others still use, and the next open of that file then crashes in H5Dget_create_plist. Opening each file only once avoids it.

    That said, I think your option 1 could make sense for HDF5-based files (engine="netcdf4" and "h5netcdf"), because there the separate handles don't actually give us the freshness that #4879 is about while another handle to the file is still open:

    • With HDF5 file locking (the default), the file can't be modified at all while it is open: writing to it from another process fails with an HDF error.
    • With HDF5_USE_FILE_LOCKING=FALSE, another process can modify it, but a second open_dataset while the first dataset is still open sees the old attributes together with the new data, because HDF5 shares its internal file object between handles to the same file. Only after all handles are closed does a new open_dataset see the new state consistently.

    So for these backends, managers with identical arguments in read mode could share one handle as long as any of them is alive (reference-counted, and closed with the last one), while a new open_dataset after all of them are closed still opens the file fresh. That would avoid the repeated opens and LRU thrashing you saw, and also the multiple-handle situation that triggers the HDF5 crash. For non-HDF5 formats like netCDF3, reopening does give fresh data, so they should keep the current behavior. Would that cover your use case?

  4. djhoese commented on Oct 12, 2026

    @djhoese
    ContributorAuthor

    Thanks for the response.

    For your case of one file with many groups, xr.open_groups(path) or xr.open_datatree(path) should already do what you want

    Yes, however this requires decoding all of those groups when only a couple may be used.

    For the HDF5 but I think it is actually case 1 from that issue and I had filed it as a separate security issue with them.

    And yes I had only been thinking about read-only use cases here.

    That said, I think your option 1 could make sense for HDF5-based files (engine="netcdf4" and "h5netcdf"), because there the separate handles don't actually give us the freshness that #4879 is about while another handle to the file is still open:

    Ok I see. So technically the 4879 solution is "broken" for HDF5 files when a handle remains open. Good to know.

    For non-HDF5 formats like netCDF3, reopening does give fresh data, so they should keep the current behavior. Would that cover your use case?

    Yes. I'm only concerned about NetCDF4/HDF5 files in my current issue. I was trying to optimize (and avoid seg faults) the reading of these datasets with a lot of files with lots of groups with lots of variables.

  5. headtr1ck commented on Oct 27, 2026

    @headtr1ck
    Collaborator

    Regarding decoding all groups with open_datatree: you can also open each file only once and still decode just the groups you need, by passing the open file to the backend store yourself:

    import h5netcdf
    import xarray as xr
    from xarray.backends import H5NetCDFStore
    
    f = h5netcdf.File(path, "r")
    ds_a = xr.open_dataset(H5NetCDFStore(f, group="group_a"))
    ds_b = xr.open_dataset(H5NetCDFStore(f, group="group_b"))
    ...
    f.close()

    or with netCDF4:

    import netCDF4
    from xarray.backends import NetCDF4DataStore
    
    nc = netCDF4.Dataset(path)
    ds_a = xr.open_dataset(NetCDF4DataStore(nc["group_a"]))

    All datasets then share one handle per file. Note that closing any of these datasets closes the shared file, so close the file yourself once you are done with all of them, instead of the individual datasets.

  6. djhoese commented on Oct 27, 2026

    @djhoese
    ContributorAuthor

    Yep, thanks. This is exactly what I'm doing now actually. It's one of the things that sparked me filing #11814.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions