A demonstration of a surprising behaviour encountered when writing entries to the empty path of a subspace in a willow store.
Find a file
2026-04-11 14:53:23 +01:00
src Fix misleading timestamps. 2026-04-11 14:46:29 +01:00
.gitignore Demonstrate behaviour, explainer in README.md 2026-04-10 12:46:58 +01:00
Cargo.lock Demonstrate behaviour, explainer in README.md 2026-04-10 12:46:58 +01:00
Cargo.toml Demonstrate behaviour, explainer in README.md 2026-04-10 12:46:58 +01:00
README.md Improve clarity of explanation, hopefully? 2026-04-11 14:53:23 +01:00

This is a minimal example demonstrating unexpected behaviour of willow25's PersistentStore with regards to pruning an entire subspace.

Expectations

Writing an entry to the empty path of a subspace should succeed provided the appropriate write capability. This operation should prune all entries from the subspace with a lesser timestamp.

Observed behaviour

Writing to the empty path of a subspace with a valid write capability results in one of two errors being reported by the store, either:

  1. StoreError(Bab(StorageDeleted)) or
  2. StoreError(Bab(Internal { err: Os { code: 2, kind: NotFound, message: "No such file or directory" }, is_fatal: false })).

Reproducing

The example program opens a store, then writes empty, valid entries to either the empy path or the path "test" to a fixed namespace and subspace in increasing time order. It accepts the following flags:

  • --early_entry / -a - Create an entry at the path "test" at a new reference timestamp (Timestamp::now()).

  • --early_prune / -b - Create an entry at the empty path, with a timestamp one millisecond later than the reference timestamp.

  • --late_entry / -c - Create an entry at the path "test", with a timestamp two milliseconds later than the reference timestamp.

  • --late_prune / -d - Create an entry at the empty path, with a timestamp three milliseconds later than the reference timestamp.

  • --remove_store / -r - Removes the store and containing directory.

In any case, the program sleeps for four milliseconds such that sequential invocations cannot produce non-monotonic timestamps.


Case 1.

Case 1 occurs when a new entry is written to the empty path and a prior entry was also written to the empty path. Reproduce with:

cargo run -- -r
cargo run -- (-a) -b (-c) -d

where parentheses indicate flags which can each be independently omitted or including without changing the observed behaviour.


Case 2.

Case 2 occurs whenever an entry at a non-empty path is pruned by another entry at the non-empty path, which is then pruned further by an entry at the empty path.

Reproduce with, e.g.:

cargo run -- -r
cargo run -- -a -c -d