Re: DatabaseModifiedErrors causing troubles

Subject: Re: DatabaseModifiedErrors causing troubles

Date: Thu, 21 Aug 2014 10:59:11 +0200



From: Gaute Hope

Gaute Hope <> wrote on Mon, 11 Aug 2014 14:17:54 +0200:
> Hi,
> I've been working on an application that keeps a read-only handle on
> the notmuch database open for a long time. In some cases when a new
> message is added along with some renames of other messages using
> 'notmuch new' while the application is running I get an Xapian
> exception: DatabaseModifiedError:
>   A Xapian exception occurred performing query: The revision being
> read has been discarded - you
>   should call Xapian::Database::reopen() and retry the operation.
> Which seems to be printed from: notmuch_query_search_threads ->
> notmuch_query_search_messages:294.
> I have not been able to make a smaller test case at the moment (this
> happens with offlineimap updating an maildir and notmuch new run
> afterwards + some tagging).
> I can work around this by checking for a NULL pointer returned from
> notmuch_query_search_threads () and re-open the database
> (notmuch_database_close () -> notmuch_database_open ()). But I have no
> way of knowing programatically if this really is the error that has
> happened. There should be some way of propagating the error
> information or (even better for my case; for notmuch to reopen the
> database), one option is the Gmime way of passing an pointer to an
> error structure that is filled up by the notmuch interface function.

Hi again,

I have not yet been able to create a test for this with a fresh db (I
must be missing out on some of the parts triggering this).

I need to keep a query open for a while (because running it takes a
while), and this keeps coming back and biting me. I currently do a test
every time I need to access the query further (load more threads..). The
test is as follows, which is the easiest and quickest test I've found to
work reliably:

    /* tries to provoke an Xapian::DatabaseModifiedError
     * returns true if db is invalid. queries will be invalid
     * afterwards, but might still respond true to notmuch_valid
     * calls.

    time_t start = clock ();
    bool invalid = false;

    /* testing */
    notmuch_query_t * q = notmuch_query_create (nm_db, "thread:fail");

    /* this will produce an error, but no way to ensure failure */
    notmuch_query_count_threads (q);

    /* this returns NULL when error */
    notmuch_threads_t * threads = notmuch_query_search_threads (q);

    if (threads == NULL) invalid = true;

    notmuch_query_destroy (q);

    float diff = (clock () - start) * 1000.0 / CLOCKS_PER_SEC;
    cout << "db: test query time: " << diff << " ms." << endl;

    if (invalid) {
      cout << "db: no longer valid, reopen required." << endl;
    } else {
      cout << "db: valid." << endl;

As mentioned before, I am basically guessing that the reason I get
threads == NULL is the DatabaseModifiedError. There is no way to know
for sure.

For portability I would suggest starting to move towards the GError
scheme provided by glib (also used by gmime). This is a somewhat major
effort though since ensuring that error propagation is done right [0] is
somewhat tricky. It provides more or less the same functionality as
exceptions do in C++.

There is also the problem of having to change the API - one way to avoid
that is to create wrappers that behave like the old API, but don't
handle errors that good. This requires the addition of addiontal _error
variations of the current set of functions. It will be a mess.

Cheers, Gaute