diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd00281..2cb4334d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 8.18.5 (unreleased) +### Implementation changes and bug fixes +- [PR #1644](https://github.com/rqlite/rqlite/pull/1644): Expose BUSY TIMEOUT on /status. + ## 8.18.4 (January 30th 2024) ### Implementation changes and bug fixes - [PR #1644](https://github.com/rqlite/rqlite/pull/1644): Remove an unnecessary memcpy during Snapshotting. diff --git a/db/db.go b/db/db.go index de6e37d9..e2eb903e 100644 --- a/db/db.go +++ b/db/db.go @@ -321,20 +321,35 @@ func (db *DB) WALSize() (int64, error) { return 0, err } -// SetBusyTimeout sets the busy timeout for the database. -func (db *DB) SetBusyTimeout(ms int) error { - _, err := db.rwDB.Exec(fmt.Sprintf("PRAGMA busy_timeout=%d", ms)) - return err +// SetBusyTimeout sets the busy timeout for the database. If a timeout is +// is less than zero it is not set. +func (db *DB) SetBusyTimeout(rwMs, roMs int) (err error) { + if rwMs >= 0 { + _, err := db.rwDB.Exec(fmt.Sprintf("PRAGMA busy_timeout=%d", rwMs)) + if err != nil { + return err + } + } + if roMs >= 0 { + _, err = db.roDB.Exec(fmt.Sprintf("PRAGMA busy_timeout=%d", roMs)) + if err != nil { + return err + } + } + return nil } // BusyTimeout returns the current busy timeout value. -func (db *DB) BusyTimeout() (int, error) { - var rwN int - err := db.rwDB.QueryRow("PRAGMA busy_timeout").Scan(&rwN) +func (db *DB) BusyTimeout() (rwMs, roMs int, err error) { + err = db.rwDB.QueryRow("PRAGMA busy_timeout").Scan(&rwMs) if err != nil { - return 0, err + return 0, 0, err } - return rwN, err + err = db.rwDB.QueryRow("PRAGMA busy_timeout").Scan(&roMs) + if err != nil { + return 0, 0, err + } + return rwMs, roMs, nil } // Checkpoint checkpoints the WAL file. If the WAL file is not enabled, this @@ -359,16 +374,16 @@ func (db *DB) CheckpointWithTimeout(mode CheckpointMode, dur time.Duration) (err }() if dur > 0 { - bt, err := db.BusyTimeout() + rwBt, _, err := db.BusyTimeout() if err != nil { return fmt.Errorf("failed to get busy_timeout on checkpointing connection: %s", err.Error()) } - if err := db.SetBusyTimeout(int(dur.Milliseconds())); err != nil { + if err := db.SetBusyTimeout(int(dur.Milliseconds()), -1); err != nil { return fmt.Errorf("failed to set busy_timeout on checkpointing connection: %s", err.Error()) } defer func() { // Reset back to default - if _, err := db.rwDB.Exec(fmt.Sprintf("PRAGMA busy_timeout=%d", bt)); err != nil { + if err := db.SetBusyTimeout(rwBt, -1); err != nil { db.logger.Printf("failed to reset busy_timeout on checkpointing connection: %s", err.Error()) } }() @@ -1115,6 +1130,7 @@ func (db *DB) pragmas() (map[string]interface{}, error) { "journal_mode", "foreign_keys", "wal_autocheckpoint", + "busy_timeout", } { var s string if err := v.QueryRow(fmt.Sprintf("PRAGMA %s", p)).Scan(&s); err != nil { diff --git a/db/db_common_test.go b/db/db_common_test.go index 2204ed14..c46f73c4 100644 --- a/db/db_common_test.go +++ b/db/db_common_test.go @@ -19,23 +19,23 @@ func testBusyTimeout(t *testing.T, db *DB) { t.Fatalf("failed to set busy_timeout: %s", err.Error()) } - bt, err := db.BusyTimeout() + rw, _, err := db.BusyTimeout() if err != nil { t.Fatalf("failed to get busy_timeout: %s", err.Error()) } - if exp, got := rbt, bt; exp != got { + if exp, got := rbt, rw; exp != got { t.Fatalf("expected busy_timeout %d, got %d", exp, got) } - rbt2 := random.Intn(10000) - if err := db.SetBusyTimeout(rbt2); err != nil { + rw2 := random.Intn(10000) + if err := db.SetBusyTimeout(rw2, 0); err != nil { t.Fatalf("failed to set busy_timeout: %s", err.Error()) } - bt, err = db.BusyTimeout() + rw, _, err = db.BusyTimeout() if err != nil { t.Fatalf("failed to get busy_timeout: %s", err.Error()) } - if exp, got := rbt2, bt; exp != got { + if exp, got := rw2, rw; exp != got { t.Fatalf("expected busy_timeout %d, got %d", exp, got) } } diff --git a/system_test/e2e/single_node.py b/system_test/e2e/single_node.py index 0d47b452..58c71cac 100644 --- a/system_test/e2e/single_node.py +++ b/system_test/e2e/single_node.py @@ -30,8 +30,8 @@ class TestSingleNode(unittest.TestCase): n = self.cluster.wait_for_leader() ro_pragmas = n.pragmas()['ro'] rw_pragmas = n.pragmas()['rw'] - self.assertEqual(ro_pragmas, d_("{'foreign_keys': '0', 'journal_mode': 'wal', 'synchronous': '0', 'wal_autocheckpoint': '1000'}")) - self.assertEqual(rw_pragmas, d_("{'foreign_keys': '0', 'journal_mode': 'wal', 'synchronous': '0', 'wal_autocheckpoint': '0'}")) + self.assertEqual(ro_pragmas, d_("{'busy_timeout': '5000', 'foreign_keys': '0', 'journal_mode': 'wal', 'synchronous': '0', 'wal_autocheckpoint': '1000'}")) + self.assertEqual(rw_pragmas, d_("{'busy_timeout': '5000', 'foreign_keys': '0', 'journal_mode': 'wal', 'synchronous': '0', 'wal_autocheckpoint': '0'}")) def test_simple_raw_queries(self): '''Test simple queries work as expected'''