diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..708475c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ + + + +# Changelog + +Notable changes to notmuch.zig. The format is loosely +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the versions are +[semantic](https://semver.org/) — while the major version is 0, a breaking +change raises the minor. + +## 0.3.0 + +### Fixed + +- **An iterator no longer panics when notmuch declines to build an item.** + `ThreadsIterator.next` and `MessagesIterator.next` asked `notmuch_threads_get` + and `notmuch_messages_get` for the item at the cursor and wrote + `orelse unreachable`, on the reasoning that `notmuch_*_valid` had just said + there was one. That is not a guarantee libnotmuch makes: the item is built + out of the database when it is asked for, and the header says plainly that + NULL comes back if the build fails. Both now return + `error.XapianException`. + + It is reachable on any archive that is read while it is written. A writer + committing underneath a running search invalidates the reader, the next item + fails to build, and a daemon serving that search died on the spot — about + once a minute under a search load with deliveries running alongside it. + +### Changed + +- `ThreadsIterator.NextError` and `MessagesIterator.NextError` gained + `XapianException`. **This is why the minor version moved**: an exhaustive + `switch` over either set no longer compiles. A caller that propagates with + `try`, or switches with an `else`, is unaffected. + + It is reported as a Xapian exception rather than as `OutOfMemory`, which is + the only cause libnotmuch's header names, because the allocator is rarely + the real one — and a caller that treats running out of memory as fatal would + give up on something it should have retried. `Directory` already maps a NULL + return the same way. + +## 0.2.0 and earlier + +Not recorded here; see the commit history. diff --git a/build.zig.zon b/build.zig.zon index b25b623..c106e73 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,7 +3,7 @@ .{ .name = .notmuch, - .version = "0.2.0", + .version = "0.3.0", .fingerprint = 0x25da472dbcafbfc8, .minimum_zig_version = "0.16.0-dev.2682+02142a54d", @@ -20,5 +20,6 @@ "src", "LICENSES", "README.md", + "CHANGELOG.md", }, } diff --git a/src/MessagesIterator.zig b/src/MessagesIterator.zig index 955be9b..bc6416f 100644 --- a/src/MessagesIterator.zig +++ b/src/MessagesIterator.zig @@ -28,6 +28,21 @@ pub const NextError = error{ /// Iteration was invalidated by the database. Re-open the database and /// try again. OperationInvalidated, + /// The item could not be built out of the database. + /// + /// `notmuch_messages_get` is documented to return NULL only for an + /// out-of-memory situation, but the message is constructed on demand out + /// of the database when it is asked for, so *any* failure to construct + /// one arrives the same way -- a read invalidated by a writer committing + /// underneath the search is how this was first seen, on a live archive + /// taking mail while somebody read it. + /// + /// Reported as a Xapian exception rather than as `OutOfMemory`, which is + /// what the header's wording would suggest, because the allocator is + /// rarely the real cause and a caller that treats running out of memory + /// as fatal would give up on something it should have retried. It is the + /// same mapping `Directory` uses for a NULL return. + XapianException, /// libnotmuch returned an undocumented status code. Unexpected, }; @@ -90,7 +105,7 @@ pub fn next(self: *const MessagesIterator) NextError!?Message { if (c.notmuch_messages_valid(messages) == 0) break :message null; defer c.notmuch_messages_move_to_next(messages); break :message .{ - .message = c.notmuch_messages_get(messages) orelse unreachable, + .message = c.notmuch_messages_get(messages) orelse return error.XapianException, }; }, .iterator_exhausted => return null, diff --git a/src/ThreadsIterator.zig b/src/ThreadsIterator.zig index 011efc9..e0e2d75 100644 --- a/src/ThreadsIterator.zig +++ b/src/ThreadsIterator.zig @@ -22,6 +22,21 @@ pub const NextError = error{ /// Iteration was invalidated by the database. Re-open the database and /// try again. OperationInvalidated, + /// The item could not be built out of the database. + /// + /// `notmuch_threads_get` is documented to return NULL only for an + /// out-of-memory situation, but the thread is constructed on demand out + /// of the database when it is asked for, so *any* failure to construct + /// one arrives the same way -- a read invalidated by a writer committing + /// underneath the search is how this was first seen, on a live archive + /// taking mail while somebody read it. + /// + /// Reported as a Xapian exception rather than as `OutOfMemory`, which is + /// what the header's wording would suggest, because the allocator is + /// rarely the real cause and a caller that treats running out of memory + /// as fatal would give up on something it should have retried. It is the + /// same mapping `Directory` uses for a NULL return. + XapianException, /// libnotmuch returned an undocumented status code. Unexpected, }; @@ -33,7 +48,7 @@ pub fn next(self: *ThreadsIterator) NextError!?Thread { if (c.notmuch_threads_valid(threads) == 0) break :thread null; defer c.notmuch_threads_move_to_next(threads); break :thread .{ - .thread = c.notmuch_threads_get(threads) orelse unreachable, + .thread = c.notmuch_threads_get(threads) orelse return error.XapianException, }; }, .iterator_exhausted => return null,