mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-25 04:18:08 +01:00
Switch from random to sequential thread identifiers.
The sequential identifiers have the advantage of being guaranteed to be unique (until we overflow a 64-bit unsigned integer), and also take up half as much space in the "notmuch search" output (16 columns rather than 32). This change also has the side effect of fixing a bug where notmuch could block on /dev/random at startup (waiting for some entropy to appear). This bug was hit hard by the test suite, (which could easily exhaust the available entropy on common systems---resulting in large delays of the test suite).
This commit is contained in:
parent
0d58d46f7a
commit
9439b217c3
4 changed files with 55 additions and 52 deletions
|
@ -27,14 +27,19 @@
|
||||||
|
|
||||||
struct _notmuch_database {
|
struct _notmuch_database {
|
||||||
notmuch_bool_t exception_reported;
|
notmuch_bool_t exception_reported;
|
||||||
|
|
||||||
char *path;
|
char *path;
|
||||||
|
|
||||||
|
notmuch_bool_t needs_upgrade;
|
||||||
notmuch_database_mode_t mode;
|
notmuch_database_mode_t mode;
|
||||||
Xapian::Database *xapian_db;
|
Xapian::Database *xapian_db;
|
||||||
|
|
||||||
|
uint64_t last_thread_id;
|
||||||
|
|
||||||
Xapian::QueryParser *query_parser;
|
Xapian::QueryParser *query_parser;
|
||||||
Xapian::TermGenerator *term_gen;
|
Xapian::TermGenerator *term_gen;
|
||||||
Xapian::ValueRangeProcessor *value_range_processor;
|
Xapian::ValueRangeProcessor *value_range_processor;
|
||||||
|
|
||||||
notmuch_bool_t needs_upgrade;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Convert tags from Xapian internal format to notmuch format.
|
/* Convert tags from Xapian internal format to notmuch format.
|
||||||
|
|
|
@ -533,6 +533,8 @@ notmuch_database_open (const char *path,
|
||||||
notmuch->needs_upgrade = FALSE;
|
notmuch->needs_upgrade = FALSE;
|
||||||
notmuch->mode = mode;
|
notmuch->mode = mode;
|
||||||
try {
|
try {
|
||||||
|
string last_thread_id;
|
||||||
|
|
||||||
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
|
if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
|
||||||
notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
|
notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
|
||||||
Xapian::DB_CREATE_OR_OPEN);
|
Xapian::DB_CREATE_OR_OPEN);
|
||||||
|
@ -567,6 +569,20 @@ notmuch_database_open (const char *path,
|
||||||
notmuch_path, version, NOTMUCH_DATABASE_VERSION);
|
notmuch_path, version, NOTMUCH_DATABASE_VERSION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
|
||||||
|
if (last_thread_id.empty ()) {
|
||||||
|
notmuch->last_thread_id = 0;
|
||||||
|
} else {
|
||||||
|
const char *str;
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
str = last_thread_id.c_str ();
|
||||||
|
notmuch->last_thread_id = strtoull (str, &end, 16);
|
||||||
|
if (*end != '\0')
|
||||||
|
INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
|
||||||
|
}
|
||||||
|
|
||||||
notmuch->query_parser = new Xapian::QueryParser;
|
notmuch->query_parser = new Xapian::QueryParser;
|
||||||
notmuch->term_gen = new Xapian::TermGenerator;
|
notmuch->term_gen = new Xapian::TermGenerator;
|
||||||
notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
|
notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
|
||||||
|
@ -1278,14 +1294,38 @@ _notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
_notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
|
||||||
|
{
|
||||||
|
/* 16 bytes (+ terminator) for hexadecimal representation of
|
||||||
|
* a 64-bit integer. */
|
||||||
|
static char thread_id[17];
|
||||||
|
Xapian::WritableDatabase *db;
|
||||||
|
|
||||||
|
db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
|
||||||
|
|
||||||
|
notmuch->last_thread_id++;
|
||||||
|
|
||||||
|
sprintf (thread_id, "%016llx", notmuch->last_thread_id);
|
||||||
|
|
||||||
|
db->set_metadata ("last_thread_id", thread_id);
|
||||||
|
|
||||||
|
return thread_id;
|
||||||
|
}
|
||||||
|
|
||||||
/* Given a (mostly empty) 'message' and its corresponding
|
/* Given a (mostly empty) 'message' and its corresponding
|
||||||
* 'message_file' link it to existing threads in the database.
|
* 'message_file' link it to existing threads in the database.
|
||||||
*
|
*
|
||||||
* We first look at 'message_file' and its link-relevant headers
|
* We first look at 'message_file' and its link-relevant headers
|
||||||
* (References and In-Reply-To) for message IDs. We also look in the
|
* (References and In-Reply-To) for message IDs. We also look in the
|
||||||
* database for existing message that reference 'message'.
|
* database for existing message that reference 'message'. In either
|
||||||
|
* case, we will assign to the current message the first thread_id
|
||||||
|
* found (through either parent or child). We will also merge any
|
||||||
|
* existing, distinct threads where this message belongs to both,
|
||||||
|
* (which is not uncommon when mesages are processed out of order).
|
||||||
*
|
*
|
||||||
* The end result is to call _notmuch_message_ensure_thread_id which
|
* Finally, if not thread ID has been found through parent or child,
|
||||||
|
* we call _notmuch_message_generate_thread_id to generate a new
|
||||||
* generates a new thread ID if the message doesn't connect to any
|
* generates a new thread ID if the message doesn't connect to any
|
||||||
* existing threads.
|
* existing threads.
|
||||||
*/
|
*/
|
||||||
|
@ -1308,8 +1348,12 @@ _notmuch_database_link_message (notmuch_database_t *notmuch,
|
||||||
if (status)
|
if (status)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
if (thread_id == NULL)
|
/* If not part of any existing thread, generate a new thread ID. */
|
||||||
_notmuch_message_ensure_thread_id (message);
|
if (thread_id == NULL) {
|
||||||
|
thread_id = _notmuch_database_generate_thread_id (notmuch);
|
||||||
|
|
||||||
|
_notmuch_message_add_term (message, "thread", thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
return NOTMUCH_STATUS_SUCCESS;
|
return NOTMUCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,13 +42,6 @@ struct _notmuch_message {
|
||||||
Xapian::Document doc;
|
Xapian::Document doc;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* "128 bits of thread-id ought to be enough for anybody" */
|
|
||||||
#define NOTMUCH_THREAD_ID_BITS 128
|
|
||||||
#define NOTMUCH_THREAD_ID_DIGITS (NOTMUCH_THREAD_ID_BITS / 4)
|
|
||||||
typedef struct _thread_id {
|
|
||||||
char str[NOTMUCH_THREAD_ID_DIGITS + 1];
|
|
||||||
} thread_id_t;
|
|
||||||
|
|
||||||
/* We end up having to call the destructor explicitly because we had
|
/* We end up having to call the destructor explicitly because we had
|
||||||
* to use "placement new" in order to initialize C++ objects within a
|
* to use "placement new" in order to initialize C++ objects within a
|
||||||
* block that we allocated with talloc. So C++ is making talloc
|
* block that we allocated with talloc. So C++ is making talloc
|
||||||
|
@ -557,45 +550,6 @@ _notmuch_message_set_date (notmuch_message_t *message,
|
||||||
Xapian::sortable_serialise (time_value));
|
Xapian::sortable_serialise (time_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
thread_id_generate (thread_id_t *thread_id)
|
|
||||||
{
|
|
||||||
static int seeded = 0;
|
|
||||||
FILE *dev_random;
|
|
||||||
uint32_t value;
|
|
||||||
char *s;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (! seeded) {
|
|
||||||
dev_random = fopen ("/dev/random", "r");
|
|
||||||
if (dev_random == NULL) {
|
|
||||||
srand (time (NULL));
|
|
||||||
} else {
|
|
||||||
fread ((void *) &value, sizeof (value), 1, dev_random);
|
|
||||||
srand (value);
|
|
||||||
fclose (dev_random);
|
|
||||||
}
|
|
||||||
seeded = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
s = thread_id->str;
|
|
||||||
for (i = 0; i < NOTMUCH_THREAD_ID_DIGITS; i += 8) {
|
|
||||||
value = rand ();
|
|
||||||
sprintf (s, "%08x", value);
|
|
||||||
s += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_notmuch_message_ensure_thread_id (notmuch_message_t *message)
|
|
||||||
{
|
|
||||||
/* If not part of any existing thread, generate a new thread_id. */
|
|
||||||
thread_id_t thread_id;
|
|
||||||
|
|
||||||
thread_id_generate (&thread_id);
|
|
||||||
_notmuch_message_add_term (message, "thread", thread_id.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Synchronize changes made to message->doc out into the database. */
|
/* Synchronize changes made to message->doc out into the database. */
|
||||||
void
|
void
|
||||||
_notmuch_message_sync (notmuch_message_t *message)
|
_notmuch_message_sync (notmuch_message_t *message)
|
||||||
|
|
|
@ -146,7 +146,7 @@ add_message ()
|
||||||
}
|
}
|
||||||
|
|
||||||
NOTMUCH_IGNORED_OUTPUT_REGEXP='^Processed [0-9]*( total)? file|Found [0-9]* total file'
|
NOTMUCH_IGNORED_OUTPUT_REGEXP='^Processed [0-9]*( total)? file|Found [0-9]* total file'
|
||||||
NOTMUCH_THREAD_ID_SQUELCH='s/thread:................................/thread:XXX/'
|
NOTMUCH_THREAD_ID_SQUELCH='s/thread:................/thread:XXX/'
|
||||||
execute_expecting ()
|
execute_expecting ()
|
||||||
{
|
{
|
||||||
args=$1
|
args=$1
|
||||||
|
|
Loading…
Reference in a new issue