Skip to content

Skip self-copy when reading uncopied lazy-loaded field#264

Merged
rayosborn merged 2 commits into
nexpy:mainfrom
rayosborn:skip-self-copy-uncopied-data
May 28, 2026
Merged

Skip self-copy when reading uncopied lazy-loaded field#264
rayosborn merged 2 commits into
nexpy:mainfrom
rayosborn:skip-self-copy-uncopied-data

Conversation

@rayosborn

Copy link
Copy Markdown
Contributor

Summary

When a file-backed NXfield is deep-copied (via NXdata.__init__, NXdata.weighted_data, etc.), NXfield.__deepcopy__ records _uncopied_data = (file, source_path) so the data can be lazily transferred to its new destination at next read. The transfer is done in NXfield._get_uncopied_data via f.copy(source_path, self.nxpath).

If the deep-copied field ends up at the same on-disk path as the source, h5py refuses the self-copy and raises RuntimeError: Unable to synchronously copy object (destination object already exists).

The case that surfaced this is the nexpy PlotDialog: when the user selects a field that already lives inside an NXdata (e.g. the summed_data_mask companion field), the dialog wraps it in a fresh in-memory NXdata named after the original group and reparents to the original grandparent. The deep-copied wrapper's nxpath collides with the source on disk, so the read crashes.

The fix is a one-line guard in _get_uncopied_data: when source and destination paths agree, the data is already where it needs to be — skip the copy and fall through to the normal readvalue. It addresses the symptom directly and also closes the door on any other code path that wraps a file-backed lazy field into a same-on-disk-path container, not just PlotDialog.

Test plan

  • New tests/test_files.py::test_read_lazy_field_after_same_path_rewrap reproduces the original RuntimeError (uses a 2k × 2k mask so the field stays lazy-loaded — small fields skip the bug because their _value is already in memory and __deepcopy__ clears _uncopied_data). The test reads cleanly after the fix and asserts the _uncopied_data reference is cleared after the read.
  • Full suite still passes (pytest tests/ — 104 tests).
  • Confirmed in nexpy: plotting a mask field (or any field contained in an NXdata) via the PlotDialog no longer raises Unable to synchronously copy object.

rayosborn and others added 2 commits May 28, 2026 12:02
When a file-backed NXfield is deep-copied (via NXdata.__init__,
NXdata.weighted_data, etc.), `__deepcopy__` records
`_uncopied_data = (file, source_path)` so the data can be lazily
transferred to its new destination at next read. If the deep-copied
field ends up at the same on-disk path as the source --
e.g. the nexpy PlotDialog wraps a field selected from inside an
NXdata in a new in-memory NXdata named after the original group and
reparents to the same grandparent -- then `_get_uncopied_data` asks
HDF5 to copy the field onto itself and h5py raises
`RuntimeError: Unable to synchronously copy object (destination
object already exists)`.

Guard `_get_uncopied_data` against this case: when the source and
destination paths agree, the data is already where it needs to be,
so skip the copy and fall through to the normal readvalue.

Tests: new tests/test_files.py::test_read_lazy_field_after_same_path_rewrap
reproduces the original crash (the field has to stay lazy-loaded,
hence the 2k x 2k mask) and asserts a clean read after the fix.
Full suite still passes (104 tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rayosborn rayosborn merged commit cb3c0c0 into nexpy:main May 28, 2026
16 checks passed
@rayosborn rayosborn deleted the skip-self-copy-uncopied-data branch May 28, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant