Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / dd / d8b7b285c0f56287be8d6c51a55081c9af35b5
1 Return-Path: <bremner@tesseract.cs.unb.ca>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5  by arlo.cworth.org (Postfix) with ESMTP id 1CACB6DE17F0\r
6  for <notmuch@notmuchmail.org>; Fri,  5 Jun 2015 10:32:36 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at cworth.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 0.232\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0.232 tagged_above=-999 required=5 tests=[AWL=0.222, \r
12  T_HEADER_FROM_DIFFERENT_DOMAINS=0.01] autolearn=disabled\r
13 Received: from arlo.cworth.org ([127.0.0.1])\r
14  by localhost (arlo.cworth.org [127.0.0.1]) (amavisd-new, port 10024)\r
15  with ESMTP id c9TU2KJknCEF for <notmuch@notmuchmail.org>;\r
16  Fri,  5 Jun 2015 10:32:34 -0700 (PDT)\r
17 Received: from mx.xen14.node3324.gplhost.com (gitolite.debian.net\r
18  [87.98.215.224])\r
19  by arlo.cworth.org (Postfix) with ESMTPS id 245696DE17E8\r
20  for <notmuch@notmuchmail.org>; Fri,  5 Jun 2015 10:32:34 -0700 (PDT)\r
21 Received: from remotemail by mx.xen14.node3324.gplhost.com with local (Exim\r
22  4.80) (envelope-from <bremner@tesseract.cs.unb.ca>)\r
23  id 1Z0vSG-0002oI-B0; Fri, 05 Jun 2015 17:30:52 +0000\r
24 Received: (nullmailer pid 24250 invoked by uid 1000); Fri, 05 Jun 2015\r
25  17:28:42 -0000\r
26 From: David Bremner <david@tethera.net>\r
27 To: notmuch@notmuchmail.org\r
28 Subject: [PATCH 2/6] lib: Add per-message last modification tracking\r
29 Date: Fri,  5 Jun 2015 19:28:34 +0200\r
30 Message-Id: <1433525318-23756-3-git-send-email-david@tethera.net>\r
31 X-Mailer: git-send-email 2.1.4\r
32 In-Reply-To: <1433525318-23756-1-git-send-email-david@tethera.net>\r
33 References: <1432936375-astroid-4-0i1n6yczs2-1520@strange>\r
34  <1433525318-23756-1-git-send-email-david@tethera.net>\r
35 X-BeenThere: notmuch@notmuchmail.org\r
36 X-Mailman-Version: 2.1.18\r
37 Precedence: list\r
38 List-Id: "Use and development of the notmuch mail system."\r
39  <notmuch.notmuchmail.org>\r
40 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
41  <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
42 List-Archive: <http://notmuchmail.org/pipermail/notmuch/>\r
43 List-Post: <mailto:notmuch@notmuchmail.org>\r
44 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
45 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
46  <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
47 X-List-Received-Date: Fri, 05 Jun 2015 17:32:36 -0000\r
48 \r
49 From: Austin Clements <amdragon@mit.edu>\r
50 \r
51 This adds a new document value that stores the revision of the last\r
52 modification to message metadata, where the revision number increases\r
53 monotonically with each database commit.\r
54 \r
55 An alternative would be to store the wall-clock time of the last\r
56 modification of each message.  In principle this is simpler and has\r
57 the advantage that any process can determine the current timestamp\r
58 without support from libnotmuch.  However, even assuming a computer's\r
59 clock never goes backward and ignoring clock skew in networked\r
60 environments, this has a fatal flaw.  Xapian uses (optimistic)\r
61 snapshot isolation, which means reads can be concurrent with writes.\r
62 Given this, consider the following time line with a write and two read\r
63 transactions:\r
64 \r
65    write  |-X-A--------------|\r
66    read 1       |---B---|\r
67    read 2                      |---|\r
68 \r
69 The write transaction modifies message X and records the wall-clock\r
70 time of the modification at A.  The writer hangs around for a while\r
71 and later commits its change.  Read 1 is concurrent with the write, so\r
72 it doesn't see the change to X.  It does some query and records the\r
73 wall-clock time of its results at B.  Transaction read 2 later starts\r
74 after the write commits and queries for changes since wall-clock time\r
75 B (say the reads are performing an incremental backup).  Even though\r
76 read 1 could not see the change to X, read 2 is told (correctly) that\r
77 X has not changed since B, the time of the last read.  In fact, X\r
78 changed before wall-clock time A, but the change was not visible until\r
79 *after* wall-clock time B, so read 2 misses the change to X.\r
80 \r
81 This is tricky to solve in full-blown snapshot isolation, but because\r
82 Xapian serializes writes, we can use a simple, monotonically\r
83 increasing database revision number.  Furthermore, maintaining this\r
84 revision number requires no more IO than a wall-clock time solution\r
85 because Xapian already maintains statistics on the upper (and lower)\r
86 bound of each value stream.\r
87 ---\r
88  lib/database-private.h | 16 +++++++++++++++-\r
89  lib/database.cc        | 49 +++++++++++++++++++++++++++++++++++++++++++++++--\r
90  lib/message.cc         | 22 ++++++++++++++++++++++\r
91  lib/notmuch-private.h  | 10 +++++++++-\r
92  4 files changed, 93 insertions(+), 4 deletions(-)\r
93 \r
94 diff --git a/lib/database-private.h b/lib/database-private.h\r
95 index 24243db..5c5a2bb 100644\r
96 --- a/lib/database-private.h\r
97 +++ b/lib/database-private.h\r
98 @@ -100,6 +100,12 @@ enum _notmuch_features {\r
99       *\r
100       * Introduced: version 3. */\r
101      NOTMUCH_FEATURE_INDEXED_MIMETYPES = 1 << 5,\r
102 +\r
103 +    /* If set, messages store the revision number of the last\r
104 +     * modification in NOTMUCH_VALUE_LAST_MOD.\r
105 +     *\r
106 +     * Introduced: version 3. */\r
107 +    NOTMUCH_FEATURE_LAST_MOD = 1 << 6,\r
108  };\r
109  \r
110  /* In C++, a named enum is its own type, so define bitwise operators\r
111 @@ -145,6 +151,8 @@ struct _notmuch_database {\r
112  \r
113      notmuch_database_mode_t mode;\r
114      int atomic_nesting;\r
115 +    /* TRUE if changes have been made in this atomic section */\r
116 +    notmuch_bool_t atomic_dirty;\r
117      Xapian::Database *xapian_db;\r
118  \r
119      /* Bit mask of features used by this database.  This is a\r
120 @@ -158,6 +166,11 @@ struct _notmuch_database {\r
121       * next library call. May be NULL */\r
122      char *status_string;\r
123  \r
124 +    /* Highest committed revision number.  Modifications are recorded\r
125 +     * under a higher revision number, which can be generated with\r
126 +     * notmuch_database_new_revision. */\r
127 +    unsigned long revision;\r
128 +\r
129      Xapian::QueryParser *query_parser;\r
130      Xapian::TermGenerator *term_gen;\r
131      Xapian::ValueRangeProcessor *value_range_processor;\r
132 @@ -179,7 +192,8 @@ struct _notmuch_database {\r
133   * will have it). */\r
134  #define NOTMUCH_FEATURES_CURRENT \\r
135      (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \\r
136 -     NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS)\r
137 +     NOTMUCH_FEATURE_BOOL_FOLDER | NOTMUCH_FEATURE_GHOSTS | \\r
138 +     NOTMUCH_FEATURE_LAST_MOD)\r
139  \r
140  /* Return the list of terms from the given iterator matching a prefix.\r
141   * The prefix will be stripped from the strings in the returned list.\r
142 diff --git a/lib/database.cc b/lib/database.cc\r
143 index 78a24f7..a68a487 100644\r
144 --- a/lib/database.cc\r
145 +++ b/lib/database.cc\r
146 @@ -101,6 +101,9 @@ typedef struct {\r
147   *\r
148   *     SUBJECT:        The value of the "Subject" header\r
149   *\r
150 + *     LAST_MOD:       The revision number as of the last tag or\r
151 + *                     filename change.\r
152 + *\r
153   * In addition, terms from the content of the message are added with\r
154   * "from", "to", "attachment", and "subject" prefixes for use by the\r
155   * user in searching. Similarly, terms from the path of the mail\r
156 @@ -310,6 +313,8 @@ static const struct {\r
157       * them. */\r
158      { NOTMUCH_FEATURE_INDEXED_MIMETYPES,\r
159        "indexed MIME types", "w"},\r
160 +    { NOTMUCH_FEATURE_LAST_MOD,\r
161 +      "modification tracking", "w"},\r
162  };\r
163  \r
164  const char *\r
165 @@ -729,6 +734,23 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)\r
166      return NOTMUCH_STATUS_SUCCESS;\r
167  }\r
168  \r
169 +/* Allocate a revision number for the next change. */\r
170 +unsigned long\r
171 +_notmuch_database_new_revision (notmuch_database_t *notmuch)\r
172 +{\r
173 +    unsigned long new_revision = notmuch->revision + 1;\r
174 +\r
175 +    /* If we're in an atomic section, hold off on updating the\r
176 +     * committed revision number until we commit the atomic section.\r
177 +     */\r
178 +    if (notmuch->atomic_nesting)\r
179 +       notmuch->atomic_dirty = TRUE;\r
180 +    else\r
181 +       notmuch->revision = new_revision;\r
182 +\r
183 +    return new_revision;\r
184 +}\r
185 +\r
186  /* Parse a database features string from the given database version.\r
187   * Returns the feature bit set.\r
188   *\r
189 @@ -890,6 +912,7 @@ notmuch_database_open_verbose (const char *path,\r
190      notmuch->atomic_nesting = 0;\r
191      try {\r
192         string last_thread_id;\r
193 +       string last_mod;\r
194  \r
195         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {\r
196             notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,\r
197 @@ -948,6 +971,14 @@ notmuch_database_open_verbose (const char *path,\r
198                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);\r
199         }\r
200  \r
201 +       /* Get current highest revision number. */\r
202 +       last_mod = notmuch->xapian_db->get_value_upper_bound (\r
203 +           NOTMUCH_VALUE_LAST_MOD);\r
204 +       if (last_mod.empty ())\r
205 +           notmuch->revision = 0;\r
206 +       else\r
207 +           notmuch->revision = Xapian::sortable_unserialise (last_mod);\r
208 +\r
209         notmuch->query_parser = new Xapian::QueryParser;\r
210         notmuch->term_gen = new Xapian::TermGenerator;\r
211         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));\r
212 @@ -1355,7 +1386,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
213  \r
214      /* Figure out how much total work we need to do. */\r
215      if (new_features &\r
216 -       (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) {\r
217 +       (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |\r
218 +        NOTMUCH_FEATURE_LAST_MOD)) {\r
219         notmuch_query_t *query = notmuch_query_create (notmuch, "");\r
220         total += notmuch_query_count_messages (query);\r
221         notmuch_query_destroy (query);\r
222 @@ -1382,7 +1414,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
223  \r
224      /* Perform per-message upgrades. */\r
225      if (new_features &\r
226 -       (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) {\r
227 +       (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER |\r
228 +        NOTMUCH_FEATURE_LAST_MOD)) {\r
229         notmuch_query_t *query = notmuch_query_create (notmuch, "");\r
230         notmuch_messages_t *messages;\r
231         notmuch_message_t *message;\r
232 @@ -1419,6 +1452,13 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
233             if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER)\r
234                 _notmuch_message_upgrade_folder (message);\r
235  \r
236 +           /* Prior to NOTMUCH_FEATURE_LAST_MOD, messages did not\r
237 +            * track modification revisions.  Give all messages a\r
238 +            * revision of 1.\r
239 +            */\r
240 +           if (new_features & NOTMUCH_FEATURE_LAST_MOD)\r
241 +               _notmuch_message_upgrade_last_mod (message);\r
242 +\r
243             _notmuch_message_sync (message);\r
244  \r
245             notmuch_message_destroy (message);\r
246 @@ -1601,6 +1641,11 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)\r
247         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;\r
248      }\r
249  \r
250 +    if (notmuch->atomic_dirty) {\r
251 +       ++notmuch->revision;\r
252 +       notmuch->atomic_dirty = FALSE;\r
253 +    }\r
254 +\r
255  DONE:\r
256      notmuch->atomic_nesting--;\r
257      return NOTMUCH_STATUS_SUCCESS;\r
258 diff --git a/lib/message.cc b/lib/message.cc\r
259 index 1ddce3c..26b5e76 100644\r
260 --- a/lib/message.cc\r
261 +++ b/lib/message.cc\r
262 @@ -998,6 +998,16 @@ _notmuch_message_set_header_values (notmuch_message_t *message,\r
263      message->modified = TRUE;\r
264  }\r
265  \r
266 +/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD.  The caller\r
267 + * must call _notmuch_message_sync. */\r
268 +void\r
269 +_notmuch_message_upgrade_last_mod (notmuch_message_t *message)\r
270 +{\r
271 +    /* _notmuch_message_sync will update the last modification\r
272 +     * revision; we just have to ask it to. */\r
273 +    message->modified = TRUE;\r
274 +}\r
275 +\r
276  /* Synchronize changes made to message->doc out into the database. */\r
277  void\r
278  _notmuch_message_sync (notmuch_message_t *message)\r
279 @@ -1010,6 +1020,18 @@ _notmuch_message_sync (notmuch_message_t *message)\r
280      if (! message->modified)\r
281         return;\r
282  \r
283 +    /* Update the last modification of this message. */\r
284 +    if (message->notmuch->features & NOTMUCH_FEATURE_LAST_MOD)\r
285 +       /* sortable_serialise gives a reasonably compact encoding,\r
286 +        * which directly translates to reduced IO when scanning the\r
287 +        * value stream.  Since it's built for doubles, we only get 53\r
288 +        * effective bits, but that's still enough for the database to\r
289 +        * last a few centuries at 1 million revisions per second. */\r
290 +       message->doc.add_value (NOTMUCH_VALUE_LAST_MOD,\r
291 +                               Xapian::sortable_serialise (\r
292 +                                   _notmuch_database_new_revision (\r
293 +                                       message->notmuch)));\r
294 +\r
295      db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);\r
296      db->replace_document (message->doc_id, message->doc);\r
297      message->modified = FALSE;\r
298 diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h\r
299 index cc9ce12..f52b4e4 100644\r
300 --- a/lib/notmuch-private.h\r
301 +++ b/lib/notmuch-private.h\r
302 @@ -107,7 +107,8 @@ typedef enum {\r
303      NOTMUCH_VALUE_TIMESTAMP = 0,\r
304      NOTMUCH_VALUE_MESSAGE_ID,\r
305      NOTMUCH_VALUE_FROM,\r
306 -    NOTMUCH_VALUE_SUBJECT\r
307 +    NOTMUCH_VALUE_SUBJECT,\r
308 +    NOTMUCH_VALUE_LAST_MOD,\r
309  } notmuch_value_t;\r
310  \r
311  /* Xapian (with flint backend) complains if we provide a term longer\r
312 @@ -194,6 +195,9 @@ void\r
313  _notmuch_database_log (notmuch_database_t *notmuch,\r
314                        const char *format, ...);\r
315  \r
316 +unsigned long\r
317 +_notmuch_database_new_revision (notmuch_database_t *notmuch);\r
318 +\r
319  const char *\r
320  _notmuch_database_relative_path (notmuch_database_t *notmuch,\r
321                                  const char *path);\r
322 @@ -305,6 +309,10 @@ _notmuch_message_set_header_values (notmuch_message_t *message,\r
323                                     const char *date,\r
324                                     const char *from,\r
325                                     const char *subject);\r
326 +\r
327 +void\r
328 +_notmuch_message_upgrade_last_mod (notmuch_message_t *message);\r
329 +\r
330  void\r
331  _notmuch_message_sync (notmuch_message_t *message);\r
332  \r
333 -- \r
334 2.1.4\r
335 \r