Use sqlite3_file_control(SQLITE_FCNTL_PERSIST_WAL) to persist WAL
Previously, Litestream would avoid closing the SQLite3 connection in order to ensure that the WAL file was not cleaned up by the database if it was the last connection. This commit changes the behavior by introducing a file control call to perform the same action. This allows us to close the database file normally in all cases.
This commit is contained in:
@@ -178,7 +178,7 @@ func (c *ReplicateCommand) Run(ctx context.Context) (err error) {
|
|||||||
// Close closes all open databases.
|
// Close closes all open databases.
|
||||||
func (c *ReplicateCommand) Close() (err error) {
|
func (c *ReplicateCommand) Close() (err error) {
|
||||||
for _, db := range c.DBs {
|
for _, db := range c.DBs {
|
||||||
if e := db.SoftClose(); e != nil {
|
if e := db.Close(); e != nil {
|
||||||
log.Printf("error closing db: path=%s err=%s", db.Path(), e)
|
log.Printf("error closing db: path=%s err=%s", db.Path(), e)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = e
|
err = e
|
||||||
|
|||||||
28
db.go
28
db.go
@@ -291,20 +291,9 @@ func (db *DB) Open() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close releases the read lock & closes the database. This method should only
|
// Close flushes outstanding WAL writes to replicas, releases the read lock,
|
||||||
// be called by tests as it causes the underlying database to be checkpointed.
|
// and closes the database.
|
||||||
func (db *DB) Close() (err error) {
|
func (db *DB) Close() (err error) {
|
||||||
return db.close(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SoftClose closes everything but the underlying db connection. This method
|
|
||||||
// is available because the binary needs to avoid closing the database on exit
|
|
||||||
// to prevent autocheckpointing.
|
|
||||||
func (db *DB) SoftClose() (err error) {
|
|
||||||
return db.close(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) close(soft bool) (err error) {
|
|
||||||
db.cancel()
|
db.cancel()
|
||||||
db.wg.Wait()
|
db.wg.Wait()
|
||||||
|
|
||||||
@@ -325,7 +314,7 @@ func (db *DB) close(soft bool) (err error) {
|
|||||||
err = e
|
err = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.Stop(!soft)
|
r.Stop(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the read lock to allow other applications to handle checkpointing.
|
// Release the read lock to allow other applications to handle checkpointing.
|
||||||
@@ -335,9 +324,7 @@ func (db *DB) close(soft bool) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only perform full close if this is not a soft close.
|
if db.db != nil {
|
||||||
// This closes the underlying database connection which can clean up the WAL.
|
|
||||||
if !soft && db.db != nil {
|
|
||||||
if e := db.db.Close(); e != nil && err == nil {
|
if e := db.db.Close(); e != nil && err == nil {
|
||||||
err = e
|
err = e
|
||||||
}
|
}
|
||||||
@@ -392,8 +379,9 @@ func (db *DB) init() (err error) {
|
|||||||
dsn := db.path
|
dsn := db.path
|
||||||
dsn += fmt.Sprintf("?_busy_timeout=%d", BusyTimeout.Milliseconds())
|
dsn += fmt.Sprintf("?_busy_timeout=%d", BusyTimeout.Milliseconds())
|
||||||
|
|
||||||
// Connect to SQLite database.
|
// Connect to SQLite database. Use the driver registered with a hook to
|
||||||
if db.db, err = sql.Open("sqlite3", dsn); err != nil {
|
// prevent WAL files from being removed.
|
||||||
|
if db.db, err = sql.Open("litestream-sqlite3", dsn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1419,7 +1407,7 @@ func applyWAL(ctx context.Context, index int, dbPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open SQLite database and force a truncating checkpoint.
|
// Open SQLite database and force a truncating checkpoint.
|
||||||
d, err := sql.Open("sqlite3", dbPath)
|
d, err := sql.Open("litestream-sqlite3", dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,8 +270,8 @@ func TestDB_Sync(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify WAL does not exist.
|
// Remove WAL file.
|
||||||
if _, err := os.Stat(db.WALPath()); !os.IsNotExist(err) {
|
if err := os.Remove(db.WALPath()); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Naming constants.
|
// Naming constants.
|
||||||
@@ -42,6 +44,25 @@ var (
|
|||||||
ErrChecksumMismatch = errors.New("invalid replica, checksum mismatch")
|
ErrChecksumMismatch = errors.New("invalid replica, checksum mismatch")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// LogWriter is the destination writer for all logging.
|
||||||
|
LogWriter = os.Stdout
|
||||||
|
|
||||||
|
// LogFlags are the flags passed to log.New().
|
||||||
|
LogFlags = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sql.Register("litestream-sqlite3", &sqlite3.SQLiteDriver{
|
||||||
|
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
||||||
|
if err := conn.SetFileControlInt("main", sqlite3.SQLITE_FCNTL_PERSIST_WAL, 1); err != nil {
|
||||||
|
return fmt.Errorf("cannot set file control: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// SnapshotIterator represents an iterator over a collection of snapshot metadata.
|
// SnapshotIterator represents an iterator over a collection of snapshot metadata.
|
||||||
type SnapshotIterator interface {
|
type SnapshotIterator interface {
|
||||||
io.Closer
|
io.Closer
|
||||||
|
|||||||
Reference in New Issue
Block a user