Database Locking in mu/mu4e
Understanding and working with mu’s database locking behavior.
How mu Locking Works
The Xapian Backend
mu uses Xapian for its search database. Xapian’s locking model:
- Multiple concurrent readers are supported
- Only one writer can exist at a time
- Attempting to open a second writer throws a
DatabaseLockError
mu server Behavior
When mu4e is running in Emacs:
- mu4e starts
mu serveras a background process mu serveropens the database in write mode and holds the lock- The lock is held “just in case” for performance (avoids costly open/close cycles)
- The lock persists until mu server exits
What Works and What Doesn’t
While mu4e is running:
✅ WORKS - Read-only operations:
mu find <query>- Search emailsmu view <path>- View email contentmu extract <path>- Extract attachmentsmu cfind <pattern>- Find contacts- Scripts that only search/read
❌ BLOCKED - Write operations:
mu index- Reindex maildir (fails with “already locked”)mu add- Add messages- Any other process trying to write
Known Issues
mu server Doesn’t Exit Cleanly
Problem: Sometimes mu server doesn’t quit properly after exiting mu4e (Issue #2198)
Symptoms:
- Error: “Unable to get write lock on ~/.cache/mu/xapian: already locked”
- Can’t restart mu4e without manual intervention
- Happens intermittently
Detection:
# Check if mu server is still running
pgrep -u $UID mu
# Check lock status directly
xapian-delve ~/.cache/mu/xapian | grep "currently open for writing"
# Shows: true (locked) or false (unlocked)
Quick Fix:
# Gracefully stop mu server
pkill -2 -u $UID mu # SIGINT for graceful shutdown
# Wait for it to release the lock
sleep 1
# Verify it's gone
pgrep -u $UID mu || echo "mu server stopped"
Workarounds
Option 1: Use emacsclient for Indexing
Instead of running mu index externally, trigger it from within mu4e:
# Check if mu server is running
if pgrep -u $UID mu > /dev/null; then
# Use emacsclient to trigger indexing
emacsclient -e '(mu4e-update-index)'
else
# Safe to run mu index directly
mu index
fi
Option 2: Smart Lock Detection in Scripts
#!/usr/bin/env bash
# Robust indexing that works whether mu4e is running or not
reindex_mu() {
if pgrep -u $UID mu > /dev/null 2>&1; then
echo "mu4e is running, using emacsclient..."
if command -v emacsclient >/dev/null 2>&1; then
emacsclient -e '(mu4e-update-index)' 2>/dev/null || {
echo "Warning: Could not trigger reindex via mu4e"
return 1
}
else
echo "Warning: emacsclient not available, skipping reindex"
return 1
fi
else
echo "Running mu index directly..."
mu index
fi
}
reindex_mu
Option 3: Read-Only Scripting
For scripts that only need to search/read emails:
- Use
mu findfreely - it works even when mu4e is running - No need to check for locks
- Script can run in parallel with mu4e
Best Practices for Scripts
1. Distinguish Read from Write Operations
# Read-only script - no lock checking needed
search_emails() {
mu find "$@" --format=json
}
# Write operation - needs lock handling
archive_and_reindex() {
# ... move/delete emails ...
# Smart reindex
if pgrep -u $UID mu > /dev/null; then
echo "Note: mu4e is running, trigger reindex from Emacs with (mu4e-update-index)"
return 0
else
mu index
fi
}
2. Provide User Feedback
if ! mu index 2>/dev/null; then
if pgrep -u $UID mu > /dev/null; then
echo "Note: mu4e is running. Run (mu4e-update-index) in Emacs to refresh."
else
echo "Error: Failed to index. Check mu database."
return 1
fi
fi
3. Graceful Degradation
# Try to reindex, but don't fail the script if it can't
mu index 2>/dev/null || {
echo "Warning: Could not reindex (mu4e may be running)"
echo "Your changes will appear after next mu4e update"
}
Alternative: Consider notmuch
If concurrent access is critical for your workflow, consider notmuch as an alternative:
Advantages:
- No persistent server process
- Better concurrent read support
- Automatic lock recovery on next run
- Can search while indexing (with occasional errors)
Trade-offs:
- Different query syntax
- Tags stored in database (not on messages)
- Must sync database across machines
See reference/Alternatives.md for a detailed comparison.
Troubleshooting
Error: “database @ ~/.cache/mu/xapian is write-locked”
Cause: mu server is running (or a stale lock exists)
Solution:
- Check if mu4e/mu server is running:
pgrep mu - If yes, close mu4e or use emacsclient for operations
- If no (stale lock), kill stale process:
pkill -2 -u $UID mu
Error: “Unable to get write lock”
Same as above - indicates active or stale lock.
mu index works in terminal but not in scripts
Cause: Script may run while mu4e is open
Solution: Add lock detection to script (see workarounds above)
References
- mu GitHub Issue #2198 - mu server doesn’t quit
- mu GitHub Issue #340 - Cannot
mu addwhile server running - Xapian Concurrency Documentation
- mu-discuss: Giant Xapian lock