fix: SQLite uses DELETE mode on WSL2 Windows filesystem (GH#920)
Auto-detect WSL2 environment with Windows filesystem paths (/mnt/c/, etc.) and fall back to DELETE journal mode instead of WAL. SQLite WAL mode does not work reliably across the WSL2/Windows 9P filesystem boundary due to shared-memory limitations. Detection checks: 1. Path matches /mnt/[a-z]/ pattern (Windows drive mount) 2. /proc/version contains microsoft or wsl Fixes #920 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: beads/crew/dave Rig: beads Role: crew
This commit is contained in:
committed by
Steve Yegge
parent
351f4c1dd7
commit
ee34003f02
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -20,6 +21,27 @@ import (
|
||||
"github.com/tetratelabs/wazero"
|
||||
)
|
||||
|
||||
// wslWindowsPathPattern matches WSL paths to Windows filesystems like /mnt/c/, /mnt/d/, etc.
|
||||
var wslWindowsPathPattern = regexp.MustCompile(`^/mnt/[a-zA-Z]/`)
|
||||
|
||||
// isWSL2WindowsPath returns true if running under WSL2 and the path is on a Windows filesystem.
|
||||
// SQLite WAL mode doesn't work reliably across the WSL2/Windows boundary (GH#920).
|
||||
func isWSL2WindowsPath(path string) bool {
|
||||
// Check if path looks like a Windows filesystem mounted in WSL (/mnt/c/, /mnt/d/, etc.)
|
||||
if !wslWindowsPathPattern.MatchString(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if we're running under WSL by examining /proc/version
|
||||
// WSL2 contains "microsoft" or "WSL" in the version string
|
||||
data, err := os.ReadFile("/proc/version")
|
||||
if err != nil {
|
||||
return false // Not Linux or can't read - not WSL
|
||||
}
|
||||
version := strings.ToLower(string(data))
|
||||
return strings.Contains(version, "microsoft") || strings.Contains(version, "wsl")
|
||||
}
|
||||
|
||||
// SQLiteStorage implements the Storage interface using SQLite
|
||||
type SQLiteStorage struct {
|
||||
db *sql.DB
|
||||
@@ -139,9 +161,15 @@ func NewWithTimeout(ctx context.Context, path string, busyTimeout time.Duration)
|
||||
}
|
||||
|
||||
// For file-based databases, enable WAL mode once after opening the connection.
|
||||
// Exception: On WSL2 with Windows filesystem (/mnt/c/), WAL doesn't work reliably
|
||||
// due to shared-memory limitations across the 9P filesystem boundary (GH#920).
|
||||
if !isInMemory {
|
||||
if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil {
|
||||
return nil, fmt.Errorf("failed to enable WAL mode: %w", err)
|
||||
journalMode := "WAL"
|
||||
if isWSL2WindowsPath(path) {
|
||||
journalMode = "DELETE" // Fallback for WSL2 Windows filesystem
|
||||
}
|
||||
if _, err := db.Exec("PRAGMA journal_mode=" + journalMode); err != nil {
|
||||
return nil, fmt.Errorf("failed to enable %s mode: %w", journalMode, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,13 +504,17 @@ func (s *SQLiteStorage) reconnect() error {
|
||||
// Restore connection pool settings
|
||||
s.configureConnectionPool(db)
|
||||
|
||||
// Re-enable WAL mode for file-based databases
|
||||
// Re-enable WAL mode for file-based databases (or DELETE for WSL2 Windows paths)
|
||||
isInMemory := s.dbPath == ":memory:" ||
|
||||
(strings.HasPrefix(s.connStr, "file:") && strings.Contains(s.connStr, "mode=memory"))
|
||||
if !isInMemory {
|
||||
if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil {
|
||||
journalMode := "WAL"
|
||||
if isWSL2WindowsPath(s.dbPath) {
|
||||
journalMode = "DELETE" // Fallback for WSL2 Windows filesystem (GH#920)
|
||||
}
|
||||
if _, err := db.Exec("PRAGMA journal_mode=" + journalMode); err != nil {
|
||||
_ = db.Close()
|
||||
return fmt.Errorf("failed to enable WAL mode on reconnect: %w", err)
|
||||
return fmt.Errorf("failed to enable %s mode on reconnect: %w", journalMode, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user