TestDB_releaseReadLock_DoubleRollback verifies that calling releaseReadLock() after the read transaction has already been rolled back does not return an error. This can happen during shutdown when concurrent checkpoint and close operations both attempt to release the read lock. Regression test for i
(t *testing.T)
| 864 | // both attempt to release the read lock. |
| 865 | // Regression test for issue #934. |
| 866 | func TestDB_releaseReadLock_DoubleRollback(t *testing.T) { |
| 867 | dir := t.TempDir() |
| 868 | dbPath := filepath.Join(dir, "db") |
| 869 | |
| 870 | db := NewDB(dbPath) |
| 871 | db.MonitorInterval = 0 |
| 872 | db.Replica = NewReplica(db) |
| 873 | db.Replica.Client = &testReplicaClient{dir: t.TempDir()} |
| 874 | db.Replica.MonitorEnabled = false |
| 875 | if err := db.Open(); err != nil { |
| 876 | t.Fatal(err) |
| 877 | } |
| 878 | |
| 879 | // Open SQL connection to create a WAL database |
| 880 | sqldb, err := sql.Open("sqlite", dbPath) |
| 881 | if err != nil { |
| 882 | t.Fatal(err) |
| 883 | } |
| 884 | defer sqldb.Close() |
| 885 | |
| 886 | if _, err := sqldb.Exec(`PRAGMA journal_mode = wal;`); err != nil { |
| 887 | t.Fatal(err) |
| 888 | } |
| 889 | |
| 890 | if _, err := sqldb.Exec(`CREATE TABLE t (id INT)`); err != nil { |
| 891 | t.Fatal(err) |
| 892 | } |
| 893 | |
| 894 | // Sync to initialize the database and acquire read lock |
| 895 | if err := db.Sync(context.Background()); err != nil { |
| 896 | t.Fatal(err) |
| 897 | } |
| 898 | |
| 899 | // Verify read transaction exists |
| 900 | if db.rtx == nil { |
| 901 | t.Fatal("expected read transaction to exist after Sync") |
| 902 | } |
| 903 | |
| 904 | // First rollback - simulates what happens in execCheckpoint() |
| 905 | if err := db.rtx.Rollback(); err != nil { |
| 906 | t.Fatalf("first rollback failed: %v", err) |
| 907 | } |
| 908 | |
| 909 | // Second call to releaseReadLock() - simulates what happens in Close() |
| 910 | // This should NOT return an error even though the transaction is already rolled back. |
| 911 | // Before the fix, this would return "sql: transaction has already been committed or rolled back" |
| 912 | if err := db.releaseReadLock(); err != nil { |
| 913 | t.Fatalf("releaseReadLock() returned error after double rollback: %v", err) |
| 914 | } |
| 915 | |
| 916 | // Clean up - set rtx to nil since we manually rolled it back |
| 917 | db.rtx = nil |
| 918 | |
| 919 | // Close should work without error |
| 920 | if err := db.Close(context.Background()); err != nil { |
| 921 | t.Fatalf("Close() failed: %v", err) |
| 922 | } |
| 923 | } |
nothing calls this directly
no test coverage detected