Skip to content

Reduce false-positive modification warnings using file size and hash - #270

Merged
rayosborn merged 1 commit into
nexpy:mainfrom
rayosborn:fix-is-modified
Oct 2, 2026
Merged

rayosborn merged 1 commit into
nexpy:mainfrom
rayosborn:fix-is-modified

Conversation

@rayosborn

@rayosborn rayosborn commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This improves the accuracy of the NXroot.is_modified() function, which
previously compared only the filesystem st_mtime to detect external
changes. The HDF5 library updates st_mtime whenever a file is opened
in write mode — even if nothing is written — and provides no internal API
for detecting actual data changes.

is_modified() now uses a three-stage check:

  1. If st_mtime has not advanced, return False immediately (unchanged
    fast path, no overhead).
  2. If st_mtime has advanced but st_size is unchanged, read the first
    64 KB of the file and compare an Adler-32 checksum against a baseline
    recorded at last open/close. If the hash matches, treat the mtime bump
    as a false positive and return False.
  3. Only if st_mtime advanced and either the size or the hash differs
    is the file reported as modified.

New additions to support the check:

  • NXFile.size property — st_size from the same stat() call already
    made by mtime
  • NXFile.file_hash(nbytes=65536) — zlib.adler32 over the leading bytes
    of the file; reads 64 KB, well under 1 ms on local storage
  • NXroot._file_size and NXroot._file_hash, recorded alongside _mtime
    at every open(), close(), reload(), and nxfile setter call
  • NXroot.serialize() persists _file_size; deserialize() restores it
    via .get() so existing serialised sessions are handled gracefully

The is_modified() method on NXroot previously compared only the
filesystem mtime to detect external changes. This caused false positives
whenever a process opened the file in write mode without writing anything
(e.g., nxcheck -a, h5py.File(path, 'a').close()), since the HDF5 library
updates the superblock consistency flags — and therefore st_mtime — on
every write-mode open.

HDF5 provides no reliable internal API for detecting actual data changes:
h5py.h5o.get_info().mtime always returns 0, and ctime tracks object
creation only (and only if track_times=True was set at file creation).

The fix adds a three-stage check to is_modified():

1. If st_mtime has not advanced, return False immediately (unchanged
   fast path).
2. If st_mtime has advanced but st_size is unchanged, read the first
   64 KB of the file and compare an Adler-32 checksum against a
   baseline recorded at last open/close. If the hash matches, treat
   the mtime bump as a false positive and return False.
3. Only if st_mtime advanced *and* either the size or the hash differs
   is the file reported as modified.

New additions to support the check:
- NXFile.size property (st_size from the same stat() call as mtime)
- NXFile.file_hash(nbytes=65536) — zlib.adler32 of leading file bytes
- NXroot._file_size and NXroot._file_hash, recorded alongside _mtime
  at every open(), close(), reload(), and nxfile setter call
- NXroot.serialize() persists _file_size; deserialize() restores it

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rayosborn
rayosborn merged commit 51f2366 into nexpy:main Oct 2, 2026
16 checks passed
@rayosborn
rayosborn deleted the fix-is-modified branch October 2, 2026 00:54
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