Skip to content

ENH: synchronize registries, units, and catalogs for free-threading - #779

Merged
d-chambers merged 3 commits into
devfrom
free-thread-locks
Jul 25, 2026
Merged

ENH: synchronize registries, units, and catalogs for free-threading#779
d-chambers merged 3 commits into
devfrom
free-thread-locks

Conversation

@d-chambers

@d-chambers d-chambers commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Description

Part 3+4 of the series superseding #763. This makes the remaining shared mutable state safe under free-threaded CPython, using one lock per piece of state rather than #763's claim/wait event graph and keyed-lock tables.

Base: current dev (#776). Follows #772 (per-entry index map) and #773 (two-tier config).

Registries

  • _FiberIOManager gets one instance RLock, held for a whole load_plugins() call including the loader invocation. A format is only marked loaded once all of its entry points are registered, so a caller can never see a multi-version format half registered (previously a second thread could observe only V1 of a two-version format and use the older reader).
  • The caches that depend on the registry (_get_prioritized_list, _get_fiber_io_by_input_type) move from functools.cache/cached_method into an invalidatable _lookup_cache cleared by register_fiberio, and publish immutable snapshots (tuple/frozenset). This also fixes a latent staleness bug: a FiberIO registered after the first prioritized-list build was previously never seen.
  • known_formats returns a frozenset; _yield_format_version/_yield_extensions snapshot under the lock rather than reading shared state between yields.
  • Copy/pickle of the manager drops the lock in __getstate__ and recreates it in __setstate__.
  • Namespace registration and lazy attachment each get one lock (class-level and module-level). Plugin loading and namespace construction stay off the registry lock; a double check after the attachment lock lets a second thread reuse the instance the first attached. _load_plugin_registry returns a FrozenDict.

Documented cost: plugin loading is now serialized, and it runs with the manager lock held. A thread importing a module that defines a FiberIO therefore waits for any in-progress load to finish. Serializing a once-per-format step was preferred over the in-flight tracking (events, wait-for cycle detection, __getstate__ pruning) that #763 needed to avoid it.

Units

dascore/units.py gets one _UNIT_LOCK around every helper that touches the mutable pint registry, and the registry is built exactly once behind that lock (replacing @cache on get_registry). Each of those helpers is itself cached, so the lock is only taken on a cache miss.

Catalog

PatchCatalog serializes its revision counter and the caches keyed on it (backend bootstrap, to_df, __len__, get_patch, add/remove, close). Kept outside the lock: patch resolution/reads, and the long directory scan in update() (only its cache invalidation is locked). The two revision-stamped caches share one small _RevisionCache helper, and _cold_live_values() documents that callers hold the lock instead of re-acquiring it. The revision object drops/recreates its lock on pickle.

PatchCatalog.__iter__ becomes the single iteration implementation: it snapshots the relation under one lock acquisition, resolves patches outside the lock, and owns the #583 skip warning. Spool.__iter__ delegates to it rather than repeating the loop with a lock per patch (this is what makes the memory-spool benchmark faster than the base).

Caller-owned shared data

Deferred out of #773 because it is not config:

  • The remaining cached-but-writable coord arrays (CoordRange.values single-sample branch, CoordSegmented._segment_offsets) are now read-only like their siblings.
  • CoordManager.coord_shapes returns a FrozenDict (Patch.coord_shapes typed Mapping).
  • Spool.get_contents() returns a caller-owned frame. Previously it handed back the catalog's cached relation, so mutating the returned dataframe corrupted the spool.

Fork safety

utils.misc gains _locked(lock_name) and _reinit_after_fork(func). The module-level locks (units, namespaces) and the FiberIO.manager lock are reinstalled in forked children, matching the config lock added in #773.

Validation

Run against dev at 1183dbcc:

  • Full suite, CPython 3.13: 8223 passed, 89 skipped, 2 xfailed.
  • Full suite, CPython 3.14.6 free-threading build with PYTHON_GIL=0: 7966 passed, 238 skipped, 2 xfailed.
  • Affected tests (io core, namespace, misc, units, catalog, spool, coords) under PYTHON_GIL=1 on the free-threading build: 4594 passed.
  • Doctests (pytest dascore --doctest-modules): 144 passed.
  • pre-commit run --all: passed.

Benchmark gates, best of 200 runs, this branch vs the exact dev base:

benchmark dev this PR delta
test_get_format 42.4 ms 42.2 ms no change (disk-bound; see below)
TestMemorySpoolBenchmarks::test_spool_from_patches_access 40.2 µs 22.1 µs -45%

The memory-spool benchmark ends up faster than the base: Spool.__iter__ used to
call catalog.get_patch(i) once per patch, and now the catalog snapshots the
relation once, so the added locking costs one acquisition per iteration instead of
one per patch.

get_format is dominated by disk IO, so the gate benchmark cannot resolve small
changes. Micro-benchmarks of the manager hot paths (no IO), best of 9 x 2000 calls:

call dev this PR
load_plugins() (already loaded) 0.18 µs 0.046 µs
load_plugins("DASDAE") 0.26 µs 0.043 µs
yield_fiberio(extension="h5") 11.0 µs 11.5 µs
yield_fiberio("DASDAE", "1") 0.93 µs 0.71 µs

The one regression left is the extension path, +0.5 µs from two lock acquisitions
and one defensive tuple copy of the extension list.

Notes

  • codex exec is over quota until 2026-07-29 16:45, so the counterpart CLI review could not be run; this was self-reviewed instead. CodeRabbit's review has been addressed (three fixes, two declined with reasoning on the threads).
  • A follow-up commit collapsed the duplicated revision caches, made the catalog own iteration, replaced the manager's defaultdict registries with plain dicts (a missing-key read used to register an empty entry), and simplified load_plugins' bookkeeping.
  • Concurrency documentation (docs/recipes/parallelization.qmd) and the free-threaded CI job come in the last PR of the series, which will also close ENH: freeze the shared entry-point loader mapping #763.

Changelog

  • changed: the format registry, unit lookups, and catalogs are synchronized so free-threaded CPython can never observe a multi-version format half-registered.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • Bug Fixes
    • Improved thread-safety for concurrent format discovery/loading, catalog access, unit parsing/conversions, and namespace attachment.
    • get_contents() now returns a caller-owned dataframe copy, so mutations no longer affect subsequent results.
    • Cached coordinate data is non-writable (including segmented offsets and coordinate values).
    • Iteration avoids exposing partially loaded/unresolvable patch states and behaves consistently under concurrent access.
  • Tests
    • Added regression and concurrency coverage for immutable outputs, caller ownership/copy-on-write behavior, catalog concurrency, format-manager safety, unit parsing, and namespace locking/fork behavior.

Replaces the heavyweight synchronization in #763 with one lock per piece
of shared mutable state:

- _FiberIOManager gets one instance RLock held across a whole
  load_plugins() call (including the loader), so no caller can see a
  multi-version format half registered. Cached lookups become
  invalidatable snapshots (frozenset/tuple) and copy/pickle drops and
  recreates the lock.
- Namespace registration and lazy attachment each get one module/class
  level RLock; plugin loading and third-party namespace construction
  stay off the registry lock, and a double check lets a second thread
  reuse the attached instance.
- units.py gets one _UNIT_LOCK around every helper that touches the
  mutable pint registry; the registry is built exactly once.
- PatchCatalog serializes its revision counter and the caches keyed on
  it; file reads, patch resolution, and directory syncs stay outside
  the lock.
- Shared data becomes caller owned: read-only cached coord arrays,
  a FrozenDict for coord_shapes, and a copied frame from
  Spool.get_contents().

Adds _locked and _reinit_after_fork helpers to utils.misc.
@d-chambers d-chambers added the ready_for_review PR is ready for review label Jul 25, 2026
@d-chambers

Copy link
Copy Markdown
Contributor Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@d-chambers, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f0430a7-803b-4e96-8b3e-07aa802a1890

📥 Commits

Reviewing files that changed from the base of the PR and between b954aad and 449c569.

📒 Files selected for processing (2)
  • dascore/core/spool.py
  • tests/test_core/test_spool.py
📝 Walkthrough

Walkthrough

The PR adds immutable coordinate and catalog-facing snapshots, caller-owned spool dataframes, and synchronization for FiberIO managers, catalogs, Pint registries, and namespaces, including lock recreation after forking and concurrency regression tests.

Changes

State ownership and concurrency

Layer / File(s) Summary
Immutable coordinate and spool outputs
dascore/core/coordmanager.py, dascore/core/coords.py, dascore/core/patch.py, dascore/core/spool.py, tests/test_core/*
Coordinate mappings and cached arrays are immutable, and Spool.get_contents() returns caller-owned dataframes while iteration delegates to the catalog.
Lock helpers and FiberIO registry synchronization
dascore/utils/misc.py, dascore/io/core.py, tests/test_utils/test_misc.py, tests/test_io/test_io_core.py
Adds dynamic lock and fork hooks, synchronizes FiberIO loading and lookups, and invalidates cached registry snapshots after registration.
Catalog revision locking and serialization
dascore/io/index/catalog.py, tests/test_io/test_index/test_catalog.py
Protects catalog initialization, cached relation access, mutations, iteration, invalidation, closing, and revision-lock pickle restoration.
Pint registry synchronization
dascore/units.py, tests/test_units.py
Serializes registry initialization, parsing, conversion, formatting, validation, and fork lock replacement.
Namespace registry and attachment synchronization
dascore/utils/namespace.py, tests/test_utils/test_namespace.py
Protects namespace registration and per-host attachment, returns immutable registry snapshots, and resets locks after fork.

Possibly related PRs

Suggested labels: IO, spool, CI, transform, documentation

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes cover the in-scope portions of #763: registries, namespaces, units, catalogs, caller-owned cached data, and concurrency regressions.
Out of Scope Changes check ✅ Passed The diff stays focused on thread-safety, immutability, caller-owned data, and supporting tests; no unrelated feature work is evident.
Docstring Coverage ✅ Passed Docstring coverage is 91.21% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title clearly summarizes the main change: synchronizing registries, units, and catalogs for free-threaded execution.
Description check ✅ Passed The description includes the required Description and Checklist sections and provides substantial implementation and validation detail.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch free-thread-locks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added IO Work for reading/writing different formats patch related to Patch class spool related to Spool class labels Jul 25, 2026
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (1183dbc) to head (449c569).

Additional details and impacted files
@@            Coverage Diff             @@
##               dev      #779    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files          164       164            
  Lines        17488     17589   +101     
==========================================
+ Hits         17488     17589   +101     
Flag Coverage Δ
network 48.31% <70.70%> (+0.16%) ⬆️
unittests 100.00% <100.00%> (ø)

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
tests/test_io/test_io_core.py (1)

522-536: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Four byte-identical _run thread harnesses; extract one shared helper (with timeouts).

The same barrier-based runner was copy-pasted into four test modules (one variant lacks the count=4 default), so a fix like adding barrier/join timeouts has to be applied four times.

  • tests/test_io/test_io_core.py#L522-L536: replace with the shared helper (e.g. a run_in_threads util in tests/ conftest or a test utility module) and drop the local copy; add count default for parity.
  • tests/test_io/test_index/test_catalog.py#L317-L331: delete the local _run and call the shared helper.
  • tests/test_units.py#L477-L491: delete the local _run and call the shared helper.
  • tests/test_utils/test_namespace.py#L267-L281: delete the local _run and call the shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_io/test_io_core.py` around lines 522 - 536, Extract the duplicated
barrier-based _run harness into one shared run_in_threads helper with a count=4
default, barrier timeouts, and thread join timeouts. In
tests/test_io/test_io_core.py:522-536, replace the local _run and update
callers; in tests/test_io/test_index/test_catalog.py:317-331,
tests/test_units.py:477-491, and tests/test_utils/test_namespace.py:267-281,
delete each local _run and use the shared helper instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dascore/core/spool.py`:
- Around line 51-60: Update _copy_public_dataframe so the pandas 2.x branch
treats copy-on-write as enabled only when pd.options.mode.copy_on_write is
exactly True; use a deep copy for "warn" and other non-True values. Add a
regression test covering the "warn" setting where supported and verifying caller
mutations do not alter the cached dataframe.

In `@dascore/io/core.py`:
- Around line 478-508: Update FiberIOManager.load_plugins to avoid holding _lock
while calling _load_entry_point: under the lock, claim/mark requested formats as
in-flight, then release it before importing loaders so callbacks can register
safely. Re-acquire _lock only for register_fiberio, completion status, and
_loaded_formats/_failed_formats updates, preserving concurrent callers’ ability
to distinguish in-flight formats and wait or return appropriately.

In `@dascore/io/index/catalog.py`:
- Around line 597-612: Move the self._syncer.ensure_updated() call out of the
self._revision.lock in the backend property flow, keeping only backend
initialization under that lock. After ensure_updated() completes, reacquire
self._revision.lock before calling self._invalidate(), preserving the existing
backend-sharing and initialization behavior while avoiding a long directory scan
under the revision lock.

In `@dascore/utils/namespace.py`:
- Line 50: Update the return expression in the namespace-building function to
pass strict=True to both the outer zip and the nested zip over package_name and
package_url, preserving the existing FrozenDict structure.

In `@tests/test_utils/test_namespace.py`:
- Around line 302-305: Update the register function to acquire the registry’s
existing lock while copying
_MethodNameSpace._registry["dascore.concurrent_test"] into a snapshot, then
build and return the set from that snapshot after releasing the lock. Preserve
the namespace creation and returned registry contents.

---

Nitpick comments:
In `@tests/test_io/test_io_core.py`:
- Around line 522-536: Extract the duplicated barrier-based _run harness into
one shared run_in_threads helper with a count=4 default, barrier timeouts, and
thread join timeouts. In tests/test_io/test_io_core.py:522-536, replace the
local _run and update callers; in
tests/test_io/test_index/test_catalog.py:317-331, tests/test_units.py:477-491,
and tests/test_utils/test_namespace.py:267-281, delete each local _run and use
the shared helper instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 198889ca-efa1-4b0e-b6a5-3d77817f6004

📥 Commits

Reviewing files that changed from the base of the PR and between 1183dbc and 0b4cba5.

📒 Files selected for processing (18)
  • dascore/core/coordmanager.py
  • dascore/core/coords.py
  • dascore/core/patch.py
  • dascore/core/spool.py
  • dascore/io/core.py
  • dascore/io/index/catalog.py
  • dascore/units.py
  • dascore/utils/misc.py
  • dascore/utils/namespace.py
  • tests/test_core/test_coord_segmented.py
  • tests/test_core/test_coordmanager.py
  • tests/test_core/test_coords.py
  • tests/test_core/test_spool.py
  • tests/test_io/test_index/test_catalog.py
  • tests/test_io/test_io_core.py
  • tests/test_units.py
  • tests/test_utils/test_misc.py
  • tests/test_utils/test_namespace.py

Comment thread dascore/core/spool.py Outdated
Comment thread dascore/io/core.py Outdated
Comment thread dascore/io/index/catalog.py
Comment thread dascore/utils/namespace.py Outdated
Comment thread tests/test_utils/test_namespace.py Outdated
Refactors:
- Collapse the duplicated revision-stamp caches in PatchCatalog into a
  small _RevisionCache (value plus the revision it was built at).
- PatchCatalog.__iter__ is now the single iteration implementation: it
  snapshots the relation under one lock acquisition, resolves patches
  outside the lock, and owns the #583 skip warning. Spool.__iter__
  delegates to it instead of repeating the loop with per-patch locking.
- _FiberIOManager registries are plain dicts, so a missing-key read can
  no longer register an empty entry.
- load_plugins expresses its bookkeeping as one set expression, and
  regains a memoized no-op for the already-loaded case (its repeat call
  is on the get_format path).

Review (#779):
- get_contents: only the literal True enables pandas copy-on-write, so
  the "warn" setting takes the deep copy again.
- _load_plugin_registry zips strictly.
- The concurrent registry test snapshots under the registry lock.
- The four copies of the thread harness become one run_in_threads
  fixture, with barrier and join timeouts so a deadlock fails instead of
  hanging.
- Document why the one-time directory scan stays under the revision
  lock (it serializes the build, and readers have nothing to read until
  it finishes) while a later update() does not.

Adds tests for the fork handlers and both dataframe-copy branches,
which were the only uncovered lines in the patch.
@coderabbitai coderabbitai Bot added CI continuous integration documentation Improvements or additions to documentation transform Related to transform operations and removed patch related to Patch class labels Jul 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/test_io/test_io_core.py (1)

547-552: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the partial-registry regression deterministic.

The auxiliary releaser unblocks the loader immediately after it enters; the barrier does not ensure the other calls have reached load_plugins while loading is stalled. This can pass even if a competing thread bypasses the lock after _loaded_formats or _all_loaded is stamped early. Instrument and wait for competing load attempts before releasing the loader.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_io/test_io_core.py` around lines 547 - 552, Update the
partial-registry regression around manager.yield_fiberio and run_in_threads to
instrument load attempts, wait until the competing threads have entered
load_plugins while the primary load remains blocked, then release the auxiliary
loader. Ensure the test deterministically exercises concurrent loading before
collecting and asserting the results.
dascore/io/index/catalog.py (1)

646-691: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

_df_cache/_live_cache survive pickling and can serve stale rows against a rebuilt backend.

__getstate__ resets _ids for rebuilt_membership catalogs specifically because In-memory backends are rebuilt on the other side with FRESH patch ids, so a stored id membership would bind to the wrong rows. The same hazard applies to _df_cache/_live_cache, but they are not cleared here.

Since to_df() returns on a cache hit (Lines 885-887) without ever touching self.backend, and _CatalogRevision.value is expected to survive pickling unchanged (only the lock is excluded/recreated per the diff summary), an unpickled catalog whose cache was warmed before pickling will return the pre-pickle DataFrame/live tuple as-is — potentially before the backend is even rebuilt, and with _patch_id values that don't match the freshly-rebuilt backend's row ids. union() (Lines 552-554) is a concrete internal consumer that trusts to_df()["_patch_id"] to match catalog.backend's current rows, so this can silently return wrong/missing records. This is reachable via the documented Spool.map per-task pickling path once get_contents()/iteration has realized the relation before pickling.

🐛 Proposed fix
     def __getstate__(self) -> dict:
         state = dict(self.__dict__)
         state["_backend"] = None
+        # Cached rows are keyed to *this* backend's row ids. A rebuilt
+        # in-memory backend on the receiving side gets fresh ids, so a
+        # stale cache must not cross the pickle boundary.
+        state["_df_cache"] = _RevisionCache()
+        state["_live_cache"] = _RevisionCache()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/io/index/catalog.py` around lines 646 - 691, Update __getstate__ to
invalidate _df_cache and _live_cache whenever pickling can rebuild or replace
the backend, especially for rebuilt_membership catalogs; ensure the unpickled
catalog cannot serve cached rows or live tuples with stale patch IDs before
backend reconstruction. Preserve caches only when the backend and its row
identity remain valid, such as the syncer case.
🧹 Nitpick comments (1)
tests/conftest.py (1)

113-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove remaining module-local copies of this fixture.

The supplied context still shows duplicate run_in_threads fixtures in tests/test_utils/test_namespace.py, tests/test_io/test_io_core.py, tests/test_io/test_index/test_catalog.py, and tests/test_units.py. Those definitions shadow this shared fixture, so future fixes will not apply consistently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/conftest.py` around lines 113 - 141, Remove the module-local
run_in_threads fixture definitions from tests/test_utils/test_namespace.py,
tests/test_io/test_io_core.py, tests/test_io/test_index/test_catalog.py, and
tests/test_units.py, leaving tests/conftest.py::run_in_threads as the shared
fixture used by those tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/conftest.py`:
- Around line 127-129: Update the worker function in _run() to catch exceptions
from func(index) and store each exception per worker; after all threads join,
re-raise the captured exception in the test thread before returning results,
while preserving successful result collection.

---

Outside diff comments:
In `@dascore/io/index/catalog.py`:
- Around line 646-691: Update __getstate__ to invalidate _df_cache and
_live_cache whenever pickling can rebuild or replace the backend, especially for
rebuilt_membership catalogs; ensure the unpickled catalog cannot serve cached
rows or live tuples with stale patch IDs before backend reconstruction. Preserve
caches only when the backend and its row identity remain valid, such as the
syncer case.

In `@tests/test_io/test_io_core.py`:
- Around line 547-552: Update the partial-registry regression around
manager.yield_fiberio and run_in_threads to instrument load attempts, wait until
the competing threads have entered load_plugins while the primary load remains
blocked, then release the auxiliary loader. Ensure the test deterministically
exercises concurrent loading before collecting and asserting the results.

---

Nitpick comments:
In `@tests/conftest.py`:
- Around line 113-141: Remove the module-local run_in_threads fixture
definitions from tests/test_utils/test_namespace.py,
tests/test_io/test_io_core.py, tests/test_io/test_index/test_catalog.py, and
tests/test_units.py, leaving tests/conftest.py::run_in_threads as the shared
fixture used by those tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 426a0cc6-7d6f-4bf0-9b9e-adf9ed7a8231

📥 Commits

Reviewing files that changed from the base of the PR and between 0b4cba5 and b954aad.

📒 Files selected for processing (10)
  • dascore/core/spool.py
  • dascore/io/core.py
  • dascore/io/index/catalog.py
  • dascore/utils/namespace.py
  • tests/conftest.py
  • tests/test_core/test_spool.py
  • tests/test_io/test_index/test_catalog.py
  • tests/test_io/test_io_core.py
  • tests/test_units.py
  • tests/test_utils/test_namespace.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/test_utils/test_namespace.py
  • tests/test_io/test_index/test_catalog.py
  • dascore/utils/namespace.py
  • dascore/io/core.py

Comment thread tests/conftest.py
Reading pd.options.mode.copy_on_write emits a deprecation warning on
every access under pandas 3, where copy-on-write can no longer be
disabled. Settle that at import from the version and only consult the
option on pandas 2, so get_contents() stays quiet on pandas 3 and the
helper keeps a single, always-executed body (the previous version branch
was the one line codecov reported as uncovered).

The copy-mode test is now parametrized over every setting pandas 2
accepts and skips where the option no longer applies.
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

✅ Documentation built:
👉 Download
Note: You must be logged in to github and a DASDAE member to access the link.

@d-chambers
d-chambers merged commit d4a21fc into dev Jul 25, 2026
27 checks passed
@d-chambers
d-chambers deleted the free-thread-locks branch July 25, 2026 18:07
@d-chambers d-chambers removed the ready_for_review PR is ready for review label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI continuous integration documentation Improvements or additions to documentation IO Work for reading/writing different formats spool related to Spool class transform Related to transform operations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant