unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* v1 _notmuch_message_ensure_metadata exception handling
@ 2017-02-25  3:45 David Bremner
  2017-02-25  3:45 ` [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError David Bremner
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: David Bremner @ 2017-02-25  3:45 UTC (permalink / raw)
  To: notmuch, eg



In id:1487583702.5ghl7kdkaw.astroid@strange.none Gaute provided a test
case that demonstrated an uncaught Xapian::DatabaseModifiedError in
_notmuch_message_ensure_metadata.

Starting with id:20170218144551.22925-2-david@tethera.net, I provided
one approach to dealing with this via error propagation. I believe the
current series is a better approach because it actually fixes the
problem (i.e. re-opens the database), and because it takes less code.

There are probably plenty of other places where the new
notmuch_database_open needs to be called.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError
  2017-02-25  3:45 v1 _notmuch_message_ensure_metadata exception handling David Bremner
@ 2017-02-25  3:45 ` David Bremner
  2017-02-25 18:49   ` Jani Nikula
  2017-02-25  3:45 ` [PATCH 2/4] lib: add notmuch_database_reopen David Bremner
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: David Bremner @ 2017-02-25  3:45 UTC (permalink / raw)
  To: notmuch, eg

There are several of these to track down, but one that is in quite a
few code paths is _notmuch_message_ensure_metadata.
---
 test/T640-database-modified.sh | 60 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100755 test/T640-database-modified.sh

diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh
new file mode 100755
index 00000000..41869eaa
--- /dev/null
+++ b/test/T640-database-modified.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+test_description="DatabaseModifiedError handling"
+. ./test-lib.sh || exit 1
+
+add_email_corpus
+
+test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_metadata"
+test_subtest_known_broken
+test_C ${MAIL_DIR} <<'EOF'
+#include <unistd.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+#include <talloc.h>
+int
+main (int argc, char **argv)
+{
+    pid_t child;
+    const char *path = argv[1];
+
+    notmuch_database_t *rw_db, *ro_db;
+    notmuch_messages_t *messages;
+    notmuch_message_t *message, *ro_message;
+    notmuch_query_t *query;
+    notmuch_tags_t *tags;
+
+    EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_ONLY, &ro_db));
+    EXPECT0 (notmuch_database_find_message (ro_db, "4EFC743A.3060609@april.org", &ro_message));
+
+    EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &rw_db));
+    query = notmuch_query_create(rw_db, "");
+    EXPECT0 (notmuch_query_search_messages_st (query, &messages));
+
+    for (int count=0;
+	 notmuch_messages_valid (messages);
+	 notmuch_messages_move_to_next (messages), count++) {
+	message = notmuch_messages_get (messages);
+	for (int i=0; i<200; i++) {
+	    char *tag_str = talloc_asprintf(rw_db, "%d", i);
+	    EXPECT0 (notmuch_message_add_tag (message, tag_str));
+	    talloc_free (tag_str);
+	}
+    }
+
+    notmuch_database_close (rw_db);
+
+    tags = notmuch_message_get_tags (ro_message);
+    if (tags)
+	printf("SUCCESS\n");
+    return 0;
+}
+EOF
+
+cat <<'EOF' >EXPECTED
+== stdout ==
+SUCCESS
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/4] lib: add notmuch_database_reopen
  2017-02-25  3:45 v1 _notmuch_message_ensure_metadata exception handling David Bremner
  2017-02-25  3:45 ` [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError David Bremner
@ 2017-02-25  3:45 ` David Bremner
  2017-02-25 18:33   ` Jani Nikula
  2017-02-25  3:45 ` [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata David Bremner
  2017-02-25  3:45 ` [PATCH 4/4] lib/message.cc: use view number to invalidate cached metadata David Bremner
  3 siblings, 1 reply; 13+ messages in thread
From: David Bremner @ 2017-02-25  3:45 UTC (permalink / raw)
  To: notmuch, eg

The main expected use is to recover from a Xapian::DatabaseChanged
exception.
---
 lib/database-private.h |  4 ++++
 lib/database.cc        | 23 +++++++++++++++++++++++
 lib/notmuch.h          | 12 ++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/lib/database-private.h b/lib/database-private.h
index 2fb60f5e..06882439 100644
--- a/lib/database-private.h
+++ b/lib/database-private.h
@@ -207,6 +207,10 @@ struct _notmuch_database {
     unsigned long revision;
     const char *uuid;
 
+    /* Keep track of the number of times the database has been re-opened
+     * (or other global invalidations of notmuch's caching)
+     */
+    unsigned long view;
     Xapian::QueryParser *query_parser;
     Xapian::TermGenerator *term_gen;
     Xapian::ValueRangeProcessor *value_range_processor;
diff --git a/lib/database.cc b/lib/database.cc
index 386dcd17..1e958b65 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -951,6 +951,7 @@ notmuch_database_open_verbose (const char *path,
 
     notmuch->mode = mode;
     notmuch->atomic_nesting = 0;
+    notmuch->view = 1;
     try {
 	string last_thread_id;
 	string last_mod;
@@ -1133,6 +1134,28 @@ notmuch_database_close (notmuch_database_t *notmuch)
     return status;
 }
 
+notmuch_status_t
+notmuch_database_reopen (notmuch_database_t *notmuch)
+{
+    if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
+	return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
+
+    try {
+	notmuch->xapian_db->reopen ();
+    } catch (const Xapian::Error &error) {
+	if (! notmuch->exception_reported) {
+	    _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
+				   error.get_msg ().c_str ());
+	    notmuch->exception_reported = TRUE;
+	    return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+	}
+    }
+
+    notmuch->view++;
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 static int
 unlink_cb (const char *path,
 	   unused (const struct stat *sb),
diff --git a/lib/notmuch.h b/lib/notmuch.h
index 16da8be9..e9ed01dd 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -363,6 +363,18 @@ notmuch_status_t
 notmuch_database_close (notmuch_database_t *database);
 
 /**
+ * Reopen a (read-only) database, synching the database view with that
+ * on disk.
+ *
+ * @returns
+ * - NOTMUCH_STATUS_UNSUPPORTED_OPERATION: database is not read only
+ * - NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occured trying to
+ *   re-open the database.
+ */
+notmuch_status_t
+notmuch_database_reopen (notmuch_database_t *database);
+
+/**
  * A callback invoked by notmuch_database_compact to notify the user
  * of the progress of the compaction process.
  */
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata
  2017-02-25  3:45 v1 _notmuch_message_ensure_metadata exception handling David Bremner
  2017-02-25  3:45 ` [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError David Bremner
  2017-02-25  3:45 ` [PATCH 2/4] lib: add notmuch_database_reopen David Bremner
@ 2017-02-25  3:45 ` David Bremner
  2017-02-25 18:42   ` Jani Nikula
  2017-02-25  3:45 ` [PATCH 4/4] lib/message.cc: use view number to invalidate cached metadata David Bremner
  3 siblings, 1 reply; 13+ messages in thread
From: David Bremner @ 2017-02-25  3:45 UTC (permalink / raw)
  To: notmuch, eg

The retries are hardcoded to a small number, and error handling aborts
than propagating errors from notmuch_database_reopen. These are both
somewhat justified by the assumption that most things that can go
wrong in Xapian::Database::reopen are rare and fatal (like running out
of memory or disk corruption).
---
 lib/message.cc                 | 152 ++++++++++++++++++++++++-----------------
 test/T640-database-modified.sh |   1 -
 2 files changed, 88 insertions(+), 65 deletions(-)

diff --git a/lib/message.cc b/lib/message.cc
index 2fb67d85..15e2f528 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -49,6 +49,9 @@ struct visible _notmuch_message {
     /* Message document modified since last sync */
     notmuch_bool_t modified;
 
+    /* last view of database the struct is synced with */
+    unsigned long last_view;
+
     Xapian::Document doc;
     Xapian::termcount termpos;
 };
@@ -110,6 +113,9 @@ _notmuch_message_create_for_document (const void *talloc_owner,
     message->flags = 0;
     message->lazy_flags = 0;
 
+    /* the message is initially not synchronized with Xapian */
+    message->last_view = 0;
+
     /* Each of these will be lazily created as needed. */
     message->message_id = NULL;
     message->thread_id = NULL;
@@ -314,6 +320,8 @@ static void
 _notmuch_message_ensure_metadata (notmuch_message_t *message)
 {
     Xapian::TermIterator i, end;
+    notmuch_bool_t success = FALSE;
+
     const char *thread_prefix = _find_prefix ("thread"),
 	*tag_prefix = _find_prefix ("tag"),
 	*id_prefix = _find_prefix ("id"),
@@ -327,73 +335,89 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message)
      * slightly more costly than looking up individual fields if only
      * one field of the message object is actually used, it's a huge
      * win as more fields are used. */
+    for (int count=0; count < 3 && !success; count++) {
+	try {
+	    i = message->doc.termlist_begin ();
+	    end = message->doc.termlist_end ();
+
+	    /* Get thread */
+	    if (!message->thread_id)
+		message->thread_id =
+		    _notmuch_message_get_term (message, i, end, thread_prefix);
+
+	    /* Get tags */
+	    assert (strcmp (thread_prefix, tag_prefix) < 0);
+	    if (!message->tag_list) {
+		message->tag_list =
+		    _notmuch_database_get_terms_with_prefix (message, i, end,
+							     tag_prefix);
+		_notmuch_string_list_sort (message->tag_list);
+	    }
 
-    i = message->doc.termlist_begin ();
-    end = message->doc.termlist_end ();
-
-    /* Get thread */
-    if (!message->thread_id)
-	message->thread_id =
-	    _notmuch_message_get_term (message, i, end, thread_prefix);
-
-    /* Get tags */
-    assert (strcmp (thread_prefix, tag_prefix) < 0);
-    if (!message->tag_list) {
-	message->tag_list =
-	    _notmuch_database_get_terms_with_prefix (message, i, end,
-						     tag_prefix);
-	_notmuch_string_list_sort (message->tag_list);
-    }
+	    /* Get id */
+	    assert (strcmp (tag_prefix, id_prefix) < 0);
+	    if (!message->message_id)
+		message->message_id =
+		    _notmuch_message_get_term (message, i, end, id_prefix);
+
+	    /* Get document type */
+	    assert (strcmp (id_prefix, type_prefix) < 0);
+	    if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) {
+		i.skip_to (type_prefix);
+		/* "T" is the prefix "type" fields.  See
+		 * BOOLEAN_PREFIX_INTERNAL. */
+		if (*i == "Tmail")
+		    NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+		else if (*i == "Tghost")
+		    NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+		else
+		    INTERNAL_ERROR ("Message without type term");
+		NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+	    }
 
-    /* Get id */
-    assert (strcmp (tag_prefix, id_prefix) < 0);
-    if (!message->message_id)
-	message->message_id =
-	    _notmuch_message_get_term (message, i, end, id_prefix);
-
-    /* Get document type */
-    assert (strcmp (id_prefix, type_prefix) < 0);
-    if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) {
-	i.skip_to (type_prefix);
-	/* "T" is the prefix "type" fields.  See
-	 * BOOLEAN_PREFIX_INTERNAL. */
-	if (*i == "Tmail")
-	    NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
-	else if (*i == "Tghost")
-	    NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
-	else
-	    INTERNAL_ERROR ("Message without type term");
-	NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
+	    /* Get filename list.  Here we get only the terms.  We lazily
+	     * expand them to full file names when needed in
+	     * _notmuch_message_ensure_filename_list. */
+	    assert (strcmp (type_prefix, filename_prefix) < 0);
+	    if (!message->filename_term_list && !message->filename_list)
+		message->filename_term_list =
+		    _notmuch_database_get_terms_with_prefix (message, i, end,
+							     filename_prefix);
+
+
+	    /* Get property terms. Mimic the setup with filenames above */
+	    assert (strcmp (filename_prefix, property_prefix) < 0);
+	    if (!message->property_map && !message->property_term_list)
+		message->property_term_list =
+		    _notmuch_database_get_terms_with_prefix (message, i, end,
+							 property_prefix);
+
+	    /* Get reply to */
+	    assert (strcmp (property_prefix, replyto_prefix) < 0);
+	    if (!message->in_reply_to)
+		message->in_reply_to =
+		    _notmuch_message_get_term (message, i, end, replyto_prefix);
+
+
+	    /* It's perfectly valid for a message to have no In-Reply-To
+	     * header. For these cases, we return an empty string. */
+	    if (!message->in_reply_to)
+		message->in_reply_to = talloc_strdup (message, "");
+
+	    /* all the way without an exception */
+	    success = TRUE;
+	} catch (const Xapian::DatabaseModifiedError &error) {
+	    notmuch_status_t status = notmuch_database_reopen (message->notmuch);
+	    if (status != NOTMUCH_STATUS_SUCCESS)
+		INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
+				notmuch_status_to_string (status));
+	    success = FALSE;
+	} catch (const Xapian::Error &error) {
+	    INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n",
+			    error.get_msg().c_str());
+	}
     }
-
-    /* Get filename list.  Here we get only the terms.  We lazily
-     * expand them to full file names when needed in
-     * _notmuch_message_ensure_filename_list. */
-    assert (strcmp (type_prefix, filename_prefix) < 0);
-    if (!message->filename_term_list && !message->filename_list)
-	message->filename_term_list =
-	    _notmuch_database_get_terms_with_prefix (message, i, end,
-						     filename_prefix);
-
-
-    /* Get property terms. Mimic the setup with filenames above */
-    assert (strcmp (filename_prefix, property_prefix) < 0);
-    if (!message->property_map && !message->property_term_list)
-	message->property_term_list =
-	    _notmuch_database_get_terms_with_prefix (message, i, end,
-						     property_prefix);
-
-    /* Get reply to */
-    assert (strcmp (property_prefix, replyto_prefix) < 0);
-    if (!message->in_reply_to)
-	message->in_reply_to =
-	    _notmuch_message_get_term (message, i, end, replyto_prefix);
-
-
-    /* It's perfectly valid for a message to have no In-Reply-To
-     * header. For these cases, we return an empty string. */
-    if (!message->in_reply_to)
-	message->in_reply_to = talloc_strdup (message, "");
+    message->last_view = message->notmuch->view;
 }
 
 void
diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh
index 41869eaa..9599417c 100755
--- a/test/T640-database-modified.sh
+++ b/test/T640-database-modified.sh
@@ -5,7 +5,6 @@ test_description="DatabaseModifiedError handling"
 add_email_corpus
 
 test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_metadata"
-test_subtest_known_broken
 test_C ${MAIL_DIR} <<'EOF'
 #include <unistd.h>
 #include <stdlib.h>
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 4/4] lib/message.cc: use view number to invalidate cached metadata
  2017-02-25  3:45 v1 _notmuch_message_ensure_metadata exception handling David Bremner
                   ` (2 preceding siblings ...)
  2017-02-25  3:45 ` [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata David Bremner
@ 2017-02-25  3:45 ` David Bremner
  2017-02-25 18:44   ` Jani Nikula
  3 siblings, 1 reply; 13+ messages in thread
From: David Bremner @ 2017-02-25  3:45 UTC (permalink / raw)
  To: notmuch, eg

Currently the view number is incremented by notmuch_database_reopen
---
 lib/message.cc | 25 +++++++++++--------------
 1 file changed, 11 insertions(+), 14 deletions(-)

diff --git a/lib/message.cc b/lib/message.cc
index 15e2f528..bfb95917 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -317,11 +317,14 @@ _notmuch_message_get_term (notmuch_message_t *message,
 }
 
 static void
-_notmuch_message_ensure_metadata (notmuch_message_t *message)
+_notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
 {
     Xapian::TermIterator i, end;
     notmuch_bool_t success = FALSE;
 
+    if ((field != NULL) && (message->last_view >= message->notmuch->view))
+	return;
+
     const char *thread_prefix = _find_prefix ("thread"),
 	*tag_prefix = _find_prefix ("tag"),
 	*id_prefix = _find_prefix ("id"),
@@ -472,8 +475,7 @@ _notmuch_message_get_doc_id (notmuch_message_t *message)
 const char *
 notmuch_message_get_message_id (notmuch_message_t *message)
 {
-    if (!message->message_id)
-	_notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->message_id);
     if (!message->message_id)
 	INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n",
 			message->doc_id);
@@ -548,16 +550,14 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
 const char *
 _notmuch_message_get_in_reply_to (notmuch_message_t *message)
 {
-    if (!message->in_reply_to)
-	_notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->in_reply_to);
     return message->in_reply_to;
 }
 
 const char *
 notmuch_message_get_thread_id (notmuch_message_t *message)
 {
-    if (!message->thread_id)
-	_notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->thread_id);
     if (!message->thread_id)
 	INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n",
 			message->doc_id);
@@ -860,8 +860,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
     if (message->filename_list)
 	return;
 
-    if (!message->filename_term_list)
-	_notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->filename_term_list);
 
     message->filename_list = _notmuch_string_list_create (message);
     node = message->filename_term_list->head;
@@ -955,7 +954,7 @@ notmuch_message_get_flag (notmuch_message_t *message,
 {
     if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
 	! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
-	_notmuch_message_ensure_metadata (message);
+	_notmuch_message_ensure_metadata (message, NULL);
 
     return NOTMUCH_TEST_BIT (message->flags, flag);
 }
@@ -996,8 +995,7 @@ notmuch_message_get_tags (notmuch_message_t *message)
 {
     notmuch_tags_t *tags;
 
-    if (!message->tag_list)
-	_notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->tag_list);
 
     tags = _notmuch_tags_create (message, message->tag_list);
     /* _notmuch_tags_create steals the reference to the tag_list, but
@@ -1833,8 +1831,7 @@ _notmuch_message_ensure_property_map (notmuch_message_t *message)
     if (message->property_map)
 	return;
 
-    if (!message->property_term_list)
-	_notmuch_message_ensure_metadata (message);
+    _notmuch_message_ensure_metadata (message, message->property_term_list);
 
     message->property_map = _notmuch_string_map_create (message);
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/4] lib: add notmuch_database_reopen
  2017-02-25  3:45 ` [PATCH 2/4] lib: add notmuch_database_reopen David Bremner
@ 2017-02-25 18:33   ` Jani Nikula
  2017-02-25 20:11     ` David Bremner
  0 siblings, 1 reply; 13+ messages in thread
From: Jani Nikula @ 2017-02-25 18:33 UTC (permalink / raw)
  To: David Bremner, notmuch, eg

On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
> The main expected use is to recover from a Xapian::DatabaseChanged
> exception.

I guess the main question here is if this should be exposed from the
library or not. If the intention is to recover *within* the library, why
add new API?

> ---
>  lib/database-private.h |  4 ++++
>  lib/database.cc        | 23 +++++++++++++++++++++++
>  lib/notmuch.h          | 12 ++++++++++++
>  3 files changed, 39 insertions(+)
>
> diff --git a/lib/database-private.h b/lib/database-private.h
> index 2fb60f5e..06882439 100644
> --- a/lib/database-private.h
> +++ b/lib/database-private.h
> @@ -207,6 +207,10 @@ struct _notmuch_database {
>      unsigned long revision;
>      const char *uuid;
>  
> +    /* Keep track of the number of times the database has been re-opened
> +     * (or other global invalidations of notmuch's caching)
> +     */
> +    unsigned long view;
>      Xapian::QueryParser *query_parser;
>      Xapian::TermGenerator *term_gen;
>      Xapian::ValueRangeProcessor *value_range_processor;
> diff --git a/lib/database.cc b/lib/database.cc
> index 386dcd17..1e958b65 100644
> --- a/lib/database.cc
> +++ b/lib/database.cc
> @@ -951,6 +951,7 @@ notmuch_database_open_verbose (const char *path,
>  
>      notmuch->mode = mode;
>      notmuch->atomic_nesting = 0;
> +    notmuch->view = 1;
>      try {
>  	string last_thread_id;
>  	string last_mod;
> @@ -1133,6 +1134,28 @@ notmuch_database_close (notmuch_database_t *notmuch)
>      return status;
>  }
>  
> +notmuch_status_t
> +notmuch_database_reopen (notmuch_database_t *notmuch)
> +{
> +    if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
> +	return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
> +
> +    try {
> +	notmuch->xapian_db->reopen ();
> +    } catch (const Xapian::Error &error) {
> +	if (! notmuch->exception_reported) {
> +	    _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
> +				   error.get_msg ().c_str ());
> +	    notmuch->exception_reported = TRUE;
> +	    return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
> +	}

Move the above return out of the if block?

> +    }
> +
> +    notmuch->view++;
> +
> +    return NOTMUCH_STATUS_SUCCESS;
> +}
> +
>  static int
>  unlink_cb (const char *path,
>  	   unused (const struct stat *sb),
> diff --git a/lib/notmuch.h b/lib/notmuch.h
> index 16da8be9..e9ed01dd 100644
> --- a/lib/notmuch.h
> +++ b/lib/notmuch.h
> @@ -363,6 +363,18 @@ notmuch_status_t
>  notmuch_database_close (notmuch_database_t *database);
>  
>  /**
> + * Reopen a (read-only) database, synching the database view with that
> + * on disk.
> + *
> + * @returns
> + * - NOTMUCH_STATUS_UNSUPPORTED_OPERATION: database is not read only
> + * - NOTMUCH_STATUS_XAPIAN_EXCEPTION: a Xapian exception occured trying to
> + *   re-open the database.
> + */
> +notmuch_status_t
> +notmuch_database_reopen (notmuch_database_t *database);
> +
> +/**
>   * A callback invoked by notmuch_database_compact to notify the user
>   * of the progress of the compaction process.
>   */
> -- 
> 2.11.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> https://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata
  2017-02-25  3:45 ` [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata David Bremner
@ 2017-02-25 18:42   ` Jani Nikula
  2017-02-25 20:21     ` David Bremner
  0 siblings, 1 reply; 13+ messages in thread
From: Jani Nikula @ 2017-02-25 18:42 UTC (permalink / raw)
  To: David Bremner, notmuch, eg

On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
> The retries are hardcoded to a small number, and error handling aborts
> than propagating errors from notmuch_database_reopen. These are both
> somewhat justified by the assumption that most things that can go
> wrong in Xapian::Database::reopen are rare and fatal (like running out
> of memory or disk corruption).

I think the sanity of the implementation hinges on that assumption. It
makes sense if you're right, but I really have no idea either way...

> ---
>  lib/message.cc                 | 152 ++++++++++++++++++++++++-----------------
>  test/T640-database-modified.sh |   1 -
>  2 files changed, 88 insertions(+), 65 deletions(-)
>
> diff --git a/lib/message.cc b/lib/message.cc
> index 2fb67d85..15e2f528 100644
> --- a/lib/message.cc
> +++ b/lib/message.cc
> @@ -49,6 +49,9 @@ struct visible _notmuch_message {
>      /* Message document modified since last sync */
>      notmuch_bool_t modified;
>  
> +    /* last view of database the struct is synced with */
> +    unsigned long last_view;
> +
>      Xapian::Document doc;
>      Xapian::termcount termpos;
>  };
> @@ -110,6 +113,9 @@ _notmuch_message_create_for_document (const void *talloc_owner,
>      message->flags = 0;
>      message->lazy_flags = 0;
>  
> +    /* the message is initially not synchronized with Xapian */
> +    message->last_view = 0;
> +
>      /* Each of these will be lazily created as needed. */
>      message->message_id = NULL;
>      message->thread_id = NULL;
> @@ -314,6 +320,8 @@ static void
>  _notmuch_message_ensure_metadata (notmuch_message_t *message)
>  {
>      Xapian::TermIterator i, end;
> +    notmuch_bool_t success = FALSE;
> +
>      const char *thread_prefix = _find_prefix ("thread"),
>  	*tag_prefix = _find_prefix ("tag"),
>  	*id_prefix = _find_prefix ("id"),
> @@ -327,73 +335,89 @@ _notmuch_message_ensure_metadata (notmuch_message_t *message)
>       * slightly more costly than looking up individual fields if only
>       * one field of the message object is actually used, it's a huge
>       * win as more fields are used. */
> +    for (int count=0; count < 3 && !success; count++) {
> +	try {
> +	    i = message->doc.termlist_begin ();
> +	    end = message->doc.termlist_end ();
> +
> +	    /* Get thread */
> +	    if (!message->thread_id)
> +		message->thread_id =
> +		    _notmuch_message_get_term (message, i, end, thread_prefix);
> +
> +	    /* Get tags */
> +	    assert (strcmp (thread_prefix, tag_prefix) < 0);
> +	    if (!message->tag_list) {
> +		message->tag_list =
> +		    _notmuch_database_get_terms_with_prefix (message, i, end,
> +							     tag_prefix);
> +		_notmuch_string_list_sort (message->tag_list);
> +	    }
>  
> -    i = message->doc.termlist_begin ();
> -    end = message->doc.termlist_end ();
> -
> -    /* Get thread */
> -    if (!message->thread_id)
> -	message->thread_id =
> -	    _notmuch_message_get_term (message, i, end, thread_prefix);
> -
> -    /* Get tags */
> -    assert (strcmp (thread_prefix, tag_prefix) < 0);
> -    if (!message->tag_list) {
> -	message->tag_list =
> -	    _notmuch_database_get_terms_with_prefix (message, i, end,
> -						     tag_prefix);
> -	_notmuch_string_list_sort (message->tag_list);
> -    }
> +	    /* Get id */
> +	    assert (strcmp (tag_prefix, id_prefix) < 0);
> +	    if (!message->message_id)
> +		message->message_id =
> +		    _notmuch_message_get_term (message, i, end, id_prefix);
> +
> +	    /* Get document type */
> +	    assert (strcmp (id_prefix, type_prefix) < 0);
> +	    if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) {
> +		i.skip_to (type_prefix);
> +		/* "T" is the prefix "type" fields.  See
> +		 * BOOLEAN_PREFIX_INTERNAL. */
> +		if (*i == "Tmail")
> +		    NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
> +		else if (*i == "Tghost")
> +		    NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
> +		else
> +		    INTERNAL_ERROR ("Message without type term");
> +		NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
> +	    }
>  
> -    /* Get id */
> -    assert (strcmp (tag_prefix, id_prefix) < 0);
> -    if (!message->message_id)
> -	message->message_id =
> -	    _notmuch_message_get_term (message, i, end, id_prefix);
> -
> -    /* Get document type */
> -    assert (strcmp (id_prefix, type_prefix) < 0);
> -    if (! NOTMUCH_TEST_BIT (message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST)) {
> -	i.skip_to (type_prefix);
> -	/* "T" is the prefix "type" fields.  See
> -	 * BOOLEAN_PREFIX_INTERNAL. */
> -	if (*i == "Tmail")
> -	    NOTMUCH_CLEAR_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
> -	else if (*i == "Tghost")
> -	    NOTMUCH_SET_BIT (&message->flags, NOTMUCH_MESSAGE_FLAG_GHOST);
> -	else
> -	    INTERNAL_ERROR ("Message without type term");
> -	NOTMUCH_SET_BIT (&message->lazy_flags, NOTMUCH_MESSAGE_FLAG_GHOST);
> +	    /* Get filename list.  Here we get only the terms.  We lazily
> +	     * expand them to full file names when needed in
> +	     * _notmuch_message_ensure_filename_list. */
> +	    assert (strcmp (type_prefix, filename_prefix) < 0);
> +	    if (!message->filename_term_list && !message->filename_list)
> +		message->filename_term_list =
> +		    _notmuch_database_get_terms_with_prefix (message, i, end,
> +							     filename_prefix);
> +
> +
> +	    /* Get property terms. Mimic the setup with filenames above */
> +	    assert (strcmp (filename_prefix, property_prefix) < 0);
> +	    if (!message->property_map && !message->property_term_list)
> +		message->property_term_list =
> +		    _notmuch_database_get_terms_with_prefix (message, i, end,
> +							 property_prefix);
> +
> +	    /* Get reply to */
> +	    assert (strcmp (property_prefix, replyto_prefix) < 0);
> +	    if (!message->in_reply_to)
> +		message->in_reply_to =
> +		    _notmuch_message_get_term (message, i, end, replyto_prefix);
> +
> +
> +	    /* It's perfectly valid for a message to have no In-Reply-To
> +	     * header. For these cases, we return an empty string. */
> +	    if (!message->in_reply_to)
> +		message->in_reply_to = talloc_strdup (message, "");
> +
> +	    /* all the way without an exception */
> +	    success = TRUE;

Nitpick, if you don't intend to use that variable to return status from
the function, you can just break here, and get rid of the variable. But
no big deal.


> +	} catch (const Xapian::DatabaseModifiedError &error) {
> +	    notmuch_status_t status = notmuch_database_reopen (message->notmuch);
> +	    if (status != NOTMUCH_STATUS_SUCCESS)
> +		INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
> +				notmuch_status_to_string (status));
> +	    success = FALSE;
> +	} catch (const Xapian::Error &error) {
> +	    INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n",
> +			    error.get_msg().c_str());
> +	}

If the assumption is that these really are rare cases (read: shouldn't
happen), INTERNAL_ERROR is an improvement over leaking the
exception. Otherwise, I think we'd need to propagate the status all the
way to the API, which would really be annoying.

I guess I think this is a worthwhile improvement no matter what.


>      }
> -
> -    /* Get filename list.  Here we get only the terms.  We lazily
> -     * expand them to full file names when needed in
> -     * _notmuch_message_ensure_filename_list. */
> -    assert (strcmp (type_prefix, filename_prefix) < 0);
> -    if (!message->filename_term_list && !message->filename_list)
> -	message->filename_term_list =
> -	    _notmuch_database_get_terms_with_prefix (message, i, end,
> -						     filename_prefix);
> -
> -
> -    /* Get property terms. Mimic the setup with filenames above */
> -    assert (strcmp (filename_prefix, property_prefix) < 0);
> -    if (!message->property_map && !message->property_term_list)
> -	message->property_term_list =
> -	    _notmuch_database_get_terms_with_prefix (message, i, end,
> -						     property_prefix);
> -
> -    /* Get reply to */
> -    assert (strcmp (property_prefix, replyto_prefix) < 0);
> -    if (!message->in_reply_to)
> -	message->in_reply_to =
> -	    _notmuch_message_get_term (message, i, end, replyto_prefix);
> -
> -
> -    /* It's perfectly valid for a message to have no In-Reply-To
> -     * header. For these cases, we return an empty string. */
> -    if (!message->in_reply_to)
> -	message->in_reply_to = talloc_strdup (message, "");
> +    message->last_view = message->notmuch->view;
>  }
>  
>  void
> diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh
> index 41869eaa..9599417c 100755
> --- a/test/T640-database-modified.sh
> +++ b/test/T640-database-modified.sh
> @@ -5,7 +5,6 @@ test_description="DatabaseModifiedError handling"
>  add_email_corpus
>  
>  test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_metadata"
> -test_subtest_known_broken
>  test_C ${MAIL_DIR} <<'EOF'
>  #include <unistd.h>
>  #include <stdlib.h>
> -- 
> 2.11.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> https://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 4/4] lib/message.cc: use view number to invalidate cached metadata
  2017-02-25  3:45 ` [PATCH 4/4] lib/message.cc: use view number to invalidate cached metadata David Bremner
@ 2017-02-25 18:44   ` Jani Nikula
  0 siblings, 0 replies; 13+ messages in thread
From: Jani Nikula @ 2017-02-25 18:44 UTC (permalink / raw)
  To: David Bremner, notmuch, eg

On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
> Currently the view number is incremented by notmuch_database_reopen
> ---
>  lib/message.cc | 25 +++++++++++--------------
>  1 file changed, 11 insertions(+), 14 deletions(-)
>
> diff --git a/lib/message.cc b/lib/message.cc
> index 15e2f528..bfb95917 100644
> --- a/lib/message.cc
> +++ b/lib/message.cc
> @@ -317,11 +317,14 @@ _notmuch_message_get_term (notmuch_message_t *message,
>  }
>  
>  static void
> -_notmuch_message_ensure_metadata (notmuch_message_t *message)
> +_notmuch_message_ensure_metadata (notmuch_message_t *message, void *field)
>  {
>      Xapian::TermIterator i, end;
>      notmuch_bool_t success = FALSE;
>  
> +    if ((field != NULL) && (message->last_view >= message->notmuch->view))

Nitpick, excessive braces for my taste!

> +	return;
> +
>      const char *thread_prefix = _find_prefix ("thread"),
>  	*tag_prefix = _find_prefix ("tag"),
>  	*id_prefix = _find_prefix ("id"),
> @@ -472,8 +475,7 @@ _notmuch_message_get_doc_id (notmuch_message_t *message)
>  const char *
>  notmuch_message_get_message_id (notmuch_message_t *message)
>  {
> -    if (!message->message_id)
> -	_notmuch_message_ensure_metadata (message);
> +    _notmuch_message_ensure_metadata (message, message->message_id);
>      if (!message->message_id)

Side note, looks like we don't consistently check the metadata we want
is really available.

All in all, LGTM.

>  	INTERNAL_ERROR ("Message with document ID of %u has no message ID.\n",
>  			message->doc_id);
> @@ -548,16 +550,14 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header)
>  const char *
>  _notmuch_message_get_in_reply_to (notmuch_message_t *message)
>  {
> -    if (!message->in_reply_to)
> -	_notmuch_message_ensure_metadata (message);
> +    _notmuch_message_ensure_metadata (message, message->in_reply_to);
>      return message->in_reply_to;
>  }
>  
>  const char *
>  notmuch_message_get_thread_id (notmuch_message_t *message)
>  {
> -    if (!message->thread_id)
> -	_notmuch_message_ensure_metadata (message);
> +    _notmuch_message_ensure_metadata (message, message->thread_id);
>      if (!message->thread_id)
>  	INTERNAL_ERROR ("Message with document ID of %u has no thread ID.\n",
>  			message->doc_id);
> @@ -860,8 +860,7 @@ _notmuch_message_ensure_filename_list (notmuch_message_t *message)
>      if (message->filename_list)
>  	return;
>  
> -    if (!message->filename_term_list)
> -	_notmuch_message_ensure_metadata (message);
> +    _notmuch_message_ensure_metadata (message, message->filename_term_list);
>  
>      message->filename_list = _notmuch_string_list_create (message);
>      node = message->filename_term_list->head;
> @@ -955,7 +954,7 @@ notmuch_message_get_flag (notmuch_message_t *message,
>  {
>      if (flag == NOTMUCH_MESSAGE_FLAG_GHOST &&
>  	! NOTMUCH_TEST_BIT (message->lazy_flags, flag))
> -	_notmuch_message_ensure_metadata (message);
> +	_notmuch_message_ensure_metadata (message, NULL);
>  
>      return NOTMUCH_TEST_BIT (message->flags, flag);
>  }
> @@ -996,8 +995,7 @@ notmuch_message_get_tags (notmuch_message_t *message)
>  {
>      notmuch_tags_t *tags;
>  
> -    if (!message->tag_list)
> -	_notmuch_message_ensure_metadata (message);
> +    _notmuch_message_ensure_metadata (message, message->tag_list);
>  
>      tags = _notmuch_tags_create (message, message->tag_list);
>      /* _notmuch_tags_create steals the reference to the tag_list, but
> @@ -1833,8 +1831,7 @@ _notmuch_message_ensure_property_map (notmuch_message_t *message)
>      if (message->property_map)
>  	return;
>  
> -    if (!message->property_term_list)
> -	_notmuch_message_ensure_metadata (message);
> +    _notmuch_message_ensure_metadata (message, message->property_term_list);
>  
>      message->property_map = _notmuch_string_map_create (message);
>  
> -- 
> 2.11.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> https://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError
  2017-02-25  3:45 ` [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError David Bremner
@ 2017-02-25 18:49   ` Jani Nikula
  2017-02-25 20:08     ` David Bremner
  0 siblings, 1 reply; 13+ messages in thread
From: Jani Nikula @ 2017-02-25 18:49 UTC (permalink / raw)
  To: David Bremner, notmuch, eg

On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
> There are several of these to track down, but one that is in quite a
> few code paths is _notmuch_message_ensure_metadata.
> ---
>  test/T640-database-modified.sh | 60 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 60 insertions(+)
>  create mode 100755 test/T640-database-modified.sh
>
> diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh
> new file mode 100755
> index 00000000..41869eaa
> --- /dev/null
> +++ b/test/T640-database-modified.sh
> @@ -0,0 +1,60 @@
> +#!/usr/bin/env bash
> +test_description="DatabaseModifiedError handling"
> +. ./test-lib.sh || exit 1
> +
> +add_email_corpus

Is the whole corpus necessary? Just add a single message?

> +
> +test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_metadata"
> +test_subtest_known_broken
> +test_C ${MAIL_DIR} <<'EOF'

I'm not entirely thrilled by the whole test_C thing, but I guess I've
lost that battle... :/

> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <notmuch-test.h>
> +#include <talloc.h>
> +int
> +main (int argc, char **argv)
> +{
> +    pid_t child;

Leftover variable?

> +    const char *path = argv[1];
> +
> +    notmuch_database_t *rw_db, *ro_db;
> +    notmuch_messages_t *messages;
> +    notmuch_message_t *message, *ro_message;
> +    notmuch_query_t *query;
> +    notmuch_tags_t *tags;
> +
> +    EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_ONLY, &ro_db));
> +    EXPECT0 (notmuch_database_find_message (ro_db, "4EFC743A.3060609@april.org", &ro_message));

How about add_message with a specific message-id, and using that here? I
think we rely too much on the test corpus and hard-code stuff from there
too much.

Otherwise, LGTM.

> +
> +    EXPECT0 (notmuch_database_open (path, NOTMUCH_DATABASE_MODE_READ_WRITE, &rw_db));
> +    query = notmuch_query_create(rw_db, "");
> +    EXPECT0 (notmuch_query_search_messages_st (query, &messages));
> +
> +    for (int count=0;
> +	 notmuch_messages_valid (messages);
> +	 notmuch_messages_move_to_next (messages), count++) {
> +	message = notmuch_messages_get (messages);
> +	for (int i=0; i<200; i++) {
> +	    char *tag_str = talloc_asprintf(rw_db, "%d", i);
> +	    EXPECT0 (notmuch_message_add_tag (message, tag_str));
> +	    talloc_free (tag_str);
> +	}
> +    }
> +
> +    notmuch_database_close (rw_db);
> +
> +    tags = notmuch_message_get_tags (ro_message);
> +    if (tags)
> +	printf("SUCCESS\n");
> +    return 0;
> +}
> +EOF
> +
> +cat <<'EOF' >EXPECTED
> +== stdout ==
> +SUCCESS
> +== stderr ==
> +EOF
> +test_expect_equal_file EXPECTED OUTPUT
> +
> +test_done
> -- 
> 2.11.0
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> https://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError
  2017-02-25 18:49   ` Jani Nikula
@ 2017-02-25 20:08     ` David Bremner
  0 siblings, 0 replies; 13+ messages in thread
From: David Bremner @ 2017-02-25 20:08 UTC (permalink / raw)
  To: Jani Nikula, notmuch, eg

Jani Nikula <jani@nikula.org> writes:
>> +
>> +add_email_corpus
>
> Is the whole corpus necessary? Just add a single message?

Good point, that's actually leftover from a previous failed attempt.

>> +test_begin_subtest "catching DatabaseModifiedError in _notmuch_message_ensure_metadata"
>> +test_subtest_known_broken
>> +test_C ${MAIL_DIR} <<'EOF'
>
> I'm not entirely thrilled by the whole test_C thing, but I guess I've
> lost that battle... :/
>
>> +#include <unistd.h>
>> +#include <stdlib.h>
>> +#include <notmuch-test.h>
>> +#include <talloc.h>
>> +int
>> +main (int argc, char **argv)
>> +{
>> +    pid_t child;
>
> Leftover variable?

correct.

> How about add_message with a specific message-id, and using that here? I
> think we rely too much on the test corpus and hard-code stuff from there
> too much.

Good idea.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/4] lib: add notmuch_database_reopen
  2017-02-25 18:33   ` Jani Nikula
@ 2017-02-25 20:11     ` David Bremner
  0 siblings, 0 replies; 13+ messages in thread
From: David Bremner @ 2017-02-25 20:11 UTC (permalink / raw)
  To: Jani Nikula, notmuch, eg

Jani Nikula <jani@nikula.org> writes:

> On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
>> The main expected use is to recover from a Xapian::DatabaseChanged
>> exception.
>
> I guess the main question here is if this should be exposed from the
> library or not. If the intention is to recover *within* the library, why
> add new API?

Yes that would work. Although as I mentioned on IRC, the API has been
proposed a few times before. I don't mind either way, it could always be
exposed later. In particular then I could feel less guilty about not yet
providing any bindings.

>> +	notmuch->xapian_db->reopen ();
>> +    } catch (const Xapian::Error &error) {
>> +	if (! notmuch->exception_reported) {
>> +	    _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
>> +				   error.get_msg ().c_str ());
>> +	    notmuch->exception_reported = TRUE;
>> +	    return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
>> +	}
>
> Move the above return out of the if block?

oh duh. good catch.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata
  2017-02-25 18:42   ` Jani Nikula
@ 2017-02-25 20:21     ` David Bremner
  2017-02-25 20:39       ` Jani Nikula
  0 siblings, 1 reply; 13+ messages in thread
From: David Bremner @ 2017-02-25 20:21 UTC (permalink / raw)
  To: Jani Nikula, notmuch, eg

Jani Nikula <jani@nikula.org> writes:

> On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
>> The retries are hardcoded to a small number, and error handling aborts
>> than propagating errors from notmuch_database_reopen. These are both
>> somewhat justified by the assumption that most things that can go
>> wrong in Xapian::Database::reopen are rare and fatal (like running out
>> of memory or disk corruption).
>
> I think the sanity of the implementation hinges on that assumption. It
> makes sense if you're right, but I really have no idea either way...

That was my conclusion from talking to Olly (xapian upstream).

24-02-2017 08:12:57 < bremner> any intuition about how likely
   Xapian::Database::reopen is to fail? I'm catching a
   DatabaseModifiedError somewhere where handling any further errors is
   tricky, and wondering about treating a failed reopen as as "the
   impossible happened, stopping"

24-02-2017 16:22:34 < olly> bremner: there should not be much scope for
 failure - stuff like out of memory or disk errors, which are probably a
 good enough excuse to stop

I could add that to the commit message?

>> +
>> +	    /* all the way without an exception */
>> +	    success = TRUE;
>
> Nitpick, if you don't intend to use that variable to return status from
> the function, you can just break here, and get rid of the variable. But
> no big deal.
>

I think I have some kind of mental block about break and continue. But
it could even be a goto, those I understand ;).

>
>> +	} catch (const Xapian::DatabaseModifiedError &error) {
>> +	    notmuch_status_t status = notmuch_database_reopen (message->notmuch);
>> +	    if (status != NOTMUCH_STATUS_SUCCESS)
>> +		INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
>> +				notmuch_status_to_string (status));
>> +	    success = FALSE;
>> +	} catch (const Xapian::Error &error) {
>> +	    INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n",
>> +			    error.get_msg().c_str());
>> +	}
>
> If the assumption is that these really are rare cases (read: shouldn't
> happen), INTERNAL_ERROR is an improvement over leaking the
> exception. Otherwise, I think we'd need to propagate the status all the
> way to the API, which would really be annoying.
>
> I guess I think this is a worthwhile improvement no matter what.

Yeah, I had a go at that in the previous longer series, but I was not
very happy with the (incomplete) results

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata
  2017-02-25 20:21     ` David Bremner
@ 2017-02-25 20:39       ` Jani Nikula
  0 siblings, 0 replies; 13+ messages in thread
From: Jani Nikula @ 2017-02-25 20:39 UTC (permalink / raw)
  To: David Bremner, notmuch, eg

On Sat, 25 Feb 2017, David Bremner <david@tethera.net> wrote:
> Jani Nikula <jani@nikula.org> writes:
>
>> On Fri, 24 Feb 2017, David Bremner <david@tethera.net> wrote:
>>> The retries are hardcoded to a small number, and error handling aborts
>>> than propagating errors from notmuch_database_reopen. These are both
>>> somewhat justified by the assumption that most things that can go
>>> wrong in Xapian::Database::reopen are rare and fatal (like running out
>>> of memory or disk corruption).
>>
>> I think the sanity of the implementation hinges on that assumption. It
>> makes sense if you're right, but I really have no idea either way...
>
> That was my conclusion from talking to Olly (xapian upstream).
>
> 24-02-2017 08:12:57 < bremner> any intuition about how likely
>    Xapian::Database::reopen is to fail? I'm catching a
>    DatabaseModifiedError somewhere where handling any further errors is
>    tricky, and wondering about treating a failed reopen as as "the
>    impossible happened, stopping"
>
> 24-02-2017 16:22:34 < olly> bremner: there should not be much scope for
>  failure - stuff like out of memory or disk errors, which are probably a
>  good enough excuse to stop
>
> I could add that to the commit message?

Yes, please, it'll come in handy when the memory of the discussion has
faded! (Like two weeks from now... ;)

>
>>> +
>>> +	    /* all the way without an exception */
>>> +	    success = TRUE;
>>
>> Nitpick, if you don't intend to use that variable to return status from
>> the function, you can just break here, and get rid of the variable. But
>> no big deal.
>>
>
> I think I have some kind of mental block about break and continue. But
> it could even be a goto, those I understand ;).

Heh, as I said, no big deal.

>
>>
>>> +	} catch (const Xapian::DatabaseModifiedError &error) {
>>> +	    notmuch_status_t status = notmuch_database_reopen (message->notmuch);
>>> +	    if (status != NOTMUCH_STATUS_SUCCESS)
>>> +		INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
>>> +				notmuch_status_to_string (status));
>>> +	    success = FALSE;
>>> +	} catch (const Xapian::Error &error) {
>>> +	    INTERNAL_ERROR ("A Xapian exception occurred fetching message metadata: %s\n",
>>> +			    error.get_msg().c_str());
>>> +	}
>>
>> If the assumption is that these really are rare cases (read: shouldn't
>> happen), INTERNAL_ERROR is an improvement over leaking the
>> exception. Otherwise, I think we'd need to propagate the status all the
>> way to the API, which would really be annoying.
>>
>> I guess I think this is a worthwhile improvement no matter what.
>
> Yeah, I had a go at that in the previous longer series, but I was not
> very happy with the (incomplete) results

Err, I'm sorry for not being clear, I meant that this patch *series* is
a worthwhile improvement as-is, even if reopen did have common and
unrecoverable failure modes (which it shouldn't). This series catches a
previously uncaught exception, tries to do something sensible about it,
and failing at that, causes an INTERNAL_ERROR. That's a huge
improvement. If we end up seeing those errors later, we can reconsider
the error propagation.


BR,
Jani.

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2017-02-25 20:39 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-25  3:45 v1 _notmuch_message_ensure_metadata exception handling David Bremner
2017-02-25  3:45 ` [PATCH 1/4] test: add known broken test for uncaught DatabaseModifiedError David Bremner
2017-02-25 18:49   ` Jani Nikula
2017-02-25 20:08     ` David Bremner
2017-02-25  3:45 ` [PATCH 2/4] lib: add notmuch_database_reopen David Bremner
2017-02-25 18:33   ` Jani Nikula
2017-02-25 20:11     ` David Bremner
2017-02-25  3:45 ` [PATCH 3/4] lib: handle DatabaseModifiedError in _n_message_ensure_metadata David Bremner
2017-02-25 18:42   ` Jani Nikula
2017-02-25 20:21     ` David Bremner
2017-02-25 20:39       ` Jani Nikula
2017-02-25  3:45 ` [PATCH 4/4] lib/message.cc: use view number to invalidate cached metadata David Bremner
2017-02-25 18:44   ` Jani Nikula

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).