Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/test-sqlite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ jobs:

- name: With sqlite_enable_tmstmpvfs
run: go test -v -tags sqlite_enable_tmstmpvfs


- name: With sqlite_enable_multithreaded_checks
run: go test -v -tags sqlite_enable_multithreaded_checks
- name: Race
run: go test -v -race ./...

Expand Down
13 changes: 13 additions & 0 deletions cgosqlite/cgosqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ int tmstmpvfs_enabled=1;
int tmstmpvfs_enabled=0;
#endif

// Enable mutex contention warnings.
#cgo sqlite_enable_multithreaded_checks CFLAGS: -DSQLITE_ENABLE_MULTITHREADED_CHECKS
#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS
int multithreaded_checks_enabled=1;
#else
int multithreaded_checks_enabled=0;
#endif

#include "cgosqlite.h"
*/
import "C"
Expand Down Expand Up @@ -529,3 +537,8 @@ func APIArmorEnabled() bool {
func TimestampVFSEnabled() bool {
return C.tmstmpvfs_enabled == 1
}

// MultithreadedChecksEnabled reports whether or not sqlite was compiled with SQLITE_ENABLE_MULTITHREADED_CHECKS.
func MultithreadedChecksEnabled() bool {
return C.multithreaded_checks_enabled == 1
}
11 changes: 11 additions & 0 deletions cgosqlite/multithreaded_checks_disabled_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build !sqlite_enable_multithreaded_checks

package cgosqlite

import (
"testing"
)

func TestMultithreadedChecksDisabled(t *testing.T) {
testMultithreadedChecks(t, false)
}
11 changes: 11 additions & 0 deletions cgosqlite/multithreaded_checks_enabled_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build sqlite_enable_multithreaded_checks

package cgosqlite

import (
"testing"
)

func TestMultithreadedChecksEnabled(t *testing.T) {
testMultithreadedChecks(t, true)
}
80 changes: 80 additions & 0 deletions cgosqlite/multithreaded_checks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//

package cgosqlite

import (
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"testing"

"github.com/tailscale/sqlite/sqliteh"
)

// testMultithreadedChecks provides a common function for testing SQLITE_ENABLE_MULTITHREADED_CHECKS.
func testMultithreadedChecks(t *testing.T, wantThreadingWarning bool) {
if wantThreadingWarning && !MultithreadedChecksEnabled() {
t.Fatal("Multithreaded checks are not enabled")
} else if !wantThreadingWarning && MultithreadedChecksEnabled() {
t.Fatal("Multithreaded checks are enabled")
}

var gotMisuseLog atomic.Bool
err := SetLogCallback(func(code sqliteh.Code, msg string) {
if code == sqliteh.SQLITE_MISUSE && msg == "illegal multi-threaded access to database connection" {
gotMisuseLog.Store(true)
}
})
if err != nil {
t.Fatal(err)
}

// Lock this goroutine to a thread (preventing other goroutines from using that thread)
runtime.LockOSThread()

flags := sqliteh.SQLITE_OPEN_READWRITE |
sqliteh.SQLITE_OPEN_CREATE |
sqliteh.SQLITE_OPEN_WAL |
sqliteh.SQLITE_OPEN_URI |
sqliteh.SQLITE_OPEN_FULLMUTEX
// sqliteh.SQLITE_OPEN_FULLMUTEX currently contention checks do not work when opening connection with SQLITE_OPEN_FULLMUTEX
db, err := Open(filepath.Join(t.TempDir(), "test.db"), flags, "")
if err != nil {
t.Fatal(err)
}
defer db.Close()

hitAPI := func() {
for i := 0; i < 1000 && !gotMisuseLog.Load(); i++ {
// Prepare a statement on this thread, mostly ignoring errors.
stmt, _, err := db.Prepare("CREATE TABLE t(c INTEGER PRIMARY KEY)", 0)
if err != nil {
continue
}
if _, err := stmt.Step(nil); err != nil {
continue
}
_ = stmt.Finalize()
}
}

// Hit API on a separate goroutine as well as in this goroutine.
// Because the original goroutine locked the OS thread, this new goroutine
// will execute on a separate thread.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
hitAPI()
}()
hitAPI()
wg.Wait()

if wantThreadingWarning && !gotMisuseLog.Load() {
t.Fatal("did not get SQLITE_MISUSE in LogCallback")
}
if !wantThreadingWarning && gotMisuseLog.Load() {
t.Fatal("got SQLITE_MISUSE in LogCallback")
}
}
2 changes: 1 addition & 1 deletion cgosqlite/sqlite3.c
Original file line number Diff line number Diff line change
Expand Up @@ -188262,7 +188262,7 @@ static int openDatabase(
db = 0;
goto opendb_out;
}
if( isThreadsafe==0 ){
if( isThreadsafe==0 || sqlite3GlobalConfig.bCoreMutex ){
sqlite3MutexWarnOnContention(db->mutex);
}
}
Expand Down
Loading