@bobfrankston/mailx-imap 0.1.94 → 0.1.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +47 -15
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -3386,24 +3386,56 @@ export class ImapManager extends EventEmitter {
|
|
|
3386
3386
|
const raw = Buffer.from(source, "utf-8");
|
|
3387
3387
|
const bodyPath = await this.bodyStore.putMessage(accountId, folderId, uid, raw);
|
|
3388
3388
|
const parsed = await extractPreview(source);
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3389
|
+
// The body_path DB write races the main thread's
|
|
3390
|
+
// writer (two-writer WAL contention since sync
|
|
3391
|
+
// moved to its own thread). That's TRANSIENT —
|
|
3392
|
+
// the .eml is already on disk; only this UPDATE
|
|
3393
|
+
// is losing the lock. Retry with backoff instead
|
|
3394
|
+
// of treating it as a poison body. Backing a
|
|
3395
|
+
// momentary lock off for 5m→12h was exactly why
|
|
3396
|
+
// prefetched bodies "weren't happening" (Bob
|
|
3397
|
+
// 2026-06-14): the .eml sat on disk but body_path
|
|
3398
|
+
// never landed, so the row looked un-downloaded
|
|
3399
|
+
// AND was suppressed from re-fetch.
|
|
3400
|
+
let wrote = false;
|
|
3401
|
+
for (let attempt = 0; attempt < 4 && !wrote; attempt++) {
|
|
3402
|
+
try {
|
|
3403
|
+
this.db.updateBodyMeta(accountId, folderId, uid, bodyPath, parsed.hasAttachments, parsed.preview);
|
|
3404
|
+
wrote = true;
|
|
3405
|
+
}
|
|
3406
|
+
catch (we) {
|
|
3407
|
+
const locked = /database is locked|sqlite_busy|busy/i.test(String(we?.message || we));
|
|
3408
|
+
if (locked && attempt < 3) {
|
|
3409
|
+
await new Promise(r => setTimeout(r, 300 * (attempt + 1)));
|
|
3410
|
+
continue;
|
|
3411
|
+
}
|
|
3412
|
+
if (locked) {
|
|
3413
|
+
// Still locked — leave the UID
|
|
3414
|
+
// un-backed-off so the next tick heals it.
|
|
3415
|
+
console.error(` [prefetch] ${accountId}/${uid}: body_path write still locked after retries — retry next tick`);
|
|
3416
|
+
}
|
|
3417
|
+
else {
|
|
3418
|
+
// Genuine write failure (corrupt body etc.) — back off.
|
|
3419
|
+
console.error(` [prefetch] ${accountId}/${uid}: store write failed: ${we?.message || we}`);
|
|
3420
|
+
this.markPrefetchEmpty(accountId, folderId, uid);
|
|
3421
|
+
}
|
|
3422
|
+
break;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (wrote) {
|
|
3426
|
+
this.emit("bodyCached", accountId, uid);
|
|
3427
|
+
this.clearPrefetchEmpty(accountId, folderId, uid); // healed — drop stale backoff
|
|
3428
|
+
counters.totalFetched++;
|
|
3429
|
+
madeProgress = true;
|
|
3430
|
+
}
|
|
3394
3431
|
}
|
|
3395
3432
|
catch (e) {
|
|
3396
3433
|
console.error(` [prefetch] ${accountId}/${uid}: store write failed: ${e.message}`);
|
|
3397
|
-
//
|
|
3398
|
-
//
|
|
3399
|
-
//
|
|
3400
|
-
//
|
|
3401
|
-
//
|
|
3402
|
-
// prefetch cycle forever, spamming the log
|
|
3403
|
-
// and wasting fetch turns (Bob 2026-05-28
|
|
3404
|
-
// "why is prefetching still broken" — UID
|
|
3405
|
-
// 59686 failing on a loop). markPrefetchEmpty
|
|
3406
|
-
// applies the 5m→30m→2h→12h backoff.
|
|
3434
|
+
// putMessage / extractPreview failure (e.g. a
|
|
3435
|
+
// corrupt body — an IMAP command leaked into the
|
|
3436
|
+
// FETCH response). Genuine poison: back off
|
|
3437
|
+
// 5m→30m→2h→12h so it isn't re-fetched on a loop
|
|
3438
|
+
// (Bob 2026-05-28, UID 59686).
|
|
3407
3439
|
this.markPrefetchEmpty(accountId, folderId, uid);
|
|
3408
3440
|
}
|
|
3409
3441
|
})());
|