Skip to content

Disable automatic GC while a remote asset is open, to avoid a deadlock - #1967

Draft
CodyCBakerPhD wants to merge 1 commit into
masterfrom
claude/determined-wright-5tcr6n
Draft

CodyCBakerPhD wants to merge 1 commit into
masterfrom
claude/determined-wright-5tcr6n

Conversation

@CodyCBakerPhD

Copy link
Copy Markdown
Contributor

Reading a remote NWB asset through RemoteReadableAsset can hang forever: a deadlock between h5py's global lock and garbage collection in fsspec's IO thread. This is the cause of the timeouts in #1450, and it made test_reextract_metadata time out in the lowest-deps job of #1955.

The deadlock

  • h5py holds its global lock while it reads from a Python file object. A read of a RemoteReadableAsset waits for fsspec's IO thread to fetch the data.
  • If a garbage collection runs in that IO thread meanwhile, it can finalize an unreferenced object that closes an HDF5 file. For example, hdmf's HDMFIO.__del__ calls HDF5IO.close(), which calls h5py's File.close(), which waits for h5py's lock.
  • Each thread then waits for the other. The aiohttp timeouts added in a6109be cannot end this, because the event loop thread itself is blocked.

The main thread's stack is the one in #1450 and in the CI failure of #1955: h5py.File() → fsspec's readinto() → sync() → Event.wait().

Whether it happens depends on when a collection happens to run, so it comes and goes with unrelated changes. On #1955 it happened on every run of the lowest-deps job, and never on master.

Changes

  • RemoteReadableAsset.open() wraps fsspec's file object in a new _GCPausedFile.
    • Automatic garbage collection is disabled while any such file is open.
    • The previous state is restored once all of them are closed (or garbage-collected).
  • No new dependencies.

Tests

  • test_gc_paused_file and test_gc_paused_file_gc_disabled (new dandi/tests/test_misctypes.py) cover nesting, closing, dropping an unclosed file, and GC being disabled beforehand.
  • test_asset_as_readable_open_pauses_gc checks that a remote asset opened from the test archive pauses GC.
  • Locally, with the lowest-deps versions from CI (hdmf 4.1.0, h5py 3.16.0, fsspec 2022.11.0):
    • A script that runs a collection in fsspec's IO thread, only if automatic GC is enabled (as automatic collection would), deadlocks without this change and completes with it.
    • test_reextract_metadata passes, also with the latest versions.
  • test_nwb2asset_remote_asset now gets past the read. It still fails, on its expected metadata (strain), so its xfail is left as is.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Qn5WBSiQgoZoL4fytF6nEr


Generated by Claude Code

h5py holds its global lock while it reads from a Python file object, and
a read of a RemoteReadableAsset waits for fsspec's IO thread to fetch
the data.  If a garbage collection runs in that thread meanwhile, the
finalizer of an unreferenced object closing an HDF5 file (e.g., hdmf's
HDMFIO.__del__ -> HDF5IO.close() -> h5py's File.close()) waits for the
same lock, and neither thread can proceed.  The aiohttp timeouts cannot
end this, as the event loop thread itself is blocked.

This is the hang of test_nwb2asset_remote_asset (#1450), and it made
test_reextract_metadata time out in the lowest-deps CI job of #1955.

RemoteReadableAsset.open() now wraps fsspec's file object in a
_GCPausedFile, which disables automatic garbage collection while any
such file is open (and restores the previous state once all are
closed).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Qn5WBSiQgoZoL4fytF6nEr
CodyCBakerPhD pushed a commit that referenced this pull request Oct 16, 2026
…talad-fuse-readable

Port the fix for the deadlock that made test_reextract_metadata time out
in the lowest-deps job.
@CodyCBakerPhD CodyCBakerPhD added bug Something isn't working patch Increment the patch version when merged labels Oct 16, 2026 — with Claude
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.88%. Comparing base (0d5f96e) to head (f137d02).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1967      +/-   ##
==========================================
+ Coverage   90.76%   90.88%   +0.12%     
==========================================
  Files          92       93       +1     
  Lines       14159    14238      +79     
==========================================
+ Hits        12851    12940      +89     
+ Misses       1308     1298      -10     
Flag Coverage Δ
unittests 90.88% <100.00%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

bug Something isn't working patch Increment the patch version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants