docs: SWMR reader (range-read M5) in CHANGELOG, README, known issues and designs
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -2,6 +2,66 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Range reads, milestone M5: reading files a SWMR writer is appending to (2026-09-27)
|
||||
Design: `docs/design/swmr.md`.
|
||||
- **Fix: files with the SWMR-write flag were bounded by a stale end of
|
||||
file** (since the end-of-file check of 2026-09-26, unreleased). A
|
||||
libhdf5 SWMR writer (h5py `f.swmr_mode = True`) does not keep
|
||||
the superblock's end-of-file address up to date; a copy h5py made of its
|
||||
own file mid-write records 715 in a 6 030-byte file.
|
||||
`Superblock::data_end` only ignored the recorded end when it lay past the
|
||||
end of the file, so such a file listed, but every chunked read failed
|
||||
("unexpected EOF: need 787 bytes, have 715") and `h5rs check` reported
|
||||
its chunk indexes past the end of the file. For a v3 superblock with the
|
||||
SWMR-write flag the data now ends at the end of the file, as libhdf5's
|
||||
SWMR reader reads it (it skips the end-of-allocation check). Every open
|
||||
path (`File::open`, `open_buffered`, `from_bytes`, `open_storage`,
|
||||
`MmapFile`, `LazyFile`, `h5rs`) reads such a copy now.
|
||||
- **`File::open_swmr(path)` / `File::open_storage_swmr(storage)`**: open a
|
||||
file a SWMR writer may still be appending to, as libhdf5's SWMR reader
|
||||
(h5py `File(path, "r", swmr=True)`) does. The file is read with
|
||||
positioned reads (the new `clawhdf5::FileStorage`: `pread`/`seek_read`,
|
||||
never mapped, `len()` the file's current length), reads are bounded by
|
||||
the file's length at the time of each read, and the chunk cache is not
|
||||
used (a cached chunk index would hide new chunks; a cached edge chunk
|
||||
would read as fill where the writer has since written). A file open for
|
||||
writing without SWMR is refused with `Error::Locked`, as libhdf5 refuses
|
||||
it.
|
||||
- **`Dataset::refresh()`** reads the dataset's object header again
|
||||
(`H5Drefresh`, h5py `Dataset.refresh()`), so `shape()` and later reads
|
||||
see the writer's appends; a handle keeps its extent until refreshed.
|
||||
- **Bounded retries.** On a SWMR-read file, an operation (open, lookups,
|
||||
refresh, reads) that fails with an error a concurrent write can cause —
|
||||
any format error except a wrong name or selection, an unsupported
|
||||
feature or a bad argument — is run again from the start, up to
|
||||
`File::swmr_read_attempts()` times (`SWMR_READ_ATTEMPTS` = 100,
|
||||
libhdf5's default metadata read attempts for SWMR readers,
|
||||
`H5Pset_metadata_read_attempts`; `set_swmr_read_attempts` changes it),
|
||||
pausing 1 µs doubling to 10 ms between attempts. Results come only from
|
||||
an attempt in which every structure verified, so a torn read is at worst
|
||||
an error, never data. `File::swmr_retries()` counts the retries.
|
||||
`File::swmr_writer_active()` reads the superblock flags again to tell
|
||||
when the writer has closed the file.
|
||||
- Tests (`crates/clawhdf5/tests/swmr_interop.rs`): the mid-write copy
|
||||
(fixture `tests/fixtures/swmr_mid_write.h5`) through every open path and
|
||||
against h5py's SWMR reader; garbled reads (a storage that corrupts the
|
||||
next reads) retried, never returned, and given up after the attempts;
|
||||
and a live test: an h5py writer appends to a 1-D and a 2-D dataset with
|
||||
one unlimited dimension (Extensible Array index, one of them gzip) and a
|
||||
2-D dataset with two (v2 B-tree index) for 2 500 steps
|
||||
(`CLAWHDF5_SWMR_STEPS`), flushing after each, while two Rust reader
|
||||
threads refresh and read all three in a loop — every value must be the
|
||||
one the writer wrote at its position, extents never shrink — beside
|
||||
h5py's own SWMR reader doing the same checks; after the writer closes,
|
||||
the live handle and a new `File::open` read exactly what h5py reads.
|
||||
Run on tank, 2026-09-27, with h5py 3.16 / HDF5 2.0:
|
||||
`CLAWHDF5_REQUIRE_INTEROP=1 CLAWHDF5_PYTHON=.venv/bin/python cargo test
|
||||
-p clawhdf5 --test swmr_interop`; also at 20 000 steps in a release
|
||||
build. A test with the chunk cache left on in live mode fails it.
|
||||
- Not covered: SWMR writing, remote SWMR (`BlockCache` caches blocks and
|
||||
`HttpStorage` pins the length), `MmapFile`/`LazyFile`, and refreshing
|
||||
groups or attributes (a SWMR writer cannot add objects or attributes).
|
||||
|
||||
### Range reads, milestone M3: remote files (2026-09-26)
|
||||
- **New crate `clawhdf5-remote`.** `open_url("http://host/file.h5")` gives
|
||||
a `clawhdf5::File` (through `File::open_storage`) that reads the file by
|
||||
|
||||
Reference in New Issue
Block a user