Skip to content

Commit d20eddd

Browse files
committed
Add a new RegisterBusyHandler method to SQLiteConn
1 parent 846fea6 commit d20eddd

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

_example/busy_handler/main.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"flag"
6+
"time"
7+
8+
"github.com/mattn/go-sqlite3"
9+
)
10+
11+
// A busy handler can be as simple as a hardcoded sleep
12+
// We use the count along with the sleep duration to decide
13+
// roughly when we've hit the timeout
14+
func simpleBusyCallback(count int) int {
15+
const timeout = 5 * time.Second
16+
const delay = 1 * time.Millisecond
17+
18+
if delay*time.Duration(count) >= timeout {
19+
// Trigger a SQLITE_BUSY error
20+
return 0
21+
}
22+
23+
time.Sleep(delay)
24+
25+
// Attempt to access the database again
26+
return 1
27+
}
28+
29+
// This is a copy of the sqliteDefaultBusyCallback implementation
30+
// from the SQLite3 source code with minor changes
31+
//
32+
// This is equivalent to the function that's used when the
33+
// busy_timeout pragma is set
34+
func defaultBusyCallback(count int) int {
35+
// All durations are in milliseconds
36+
const timeout = 5000
37+
delays := [...]int{1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100}
38+
totals := [...]int{0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228}
39+
40+
var delay, prior int
41+
if count < len(delays) {
42+
delay = delays[count]
43+
prior = totals[count]
44+
} else {
45+
delay = delays[len(delays)-1]
46+
prior = totals[len(totals)-1] + delay*(count-len(totals))
47+
}
48+
49+
if prior+delay > timeout {
50+
delay = timeout - prior
51+
52+
if delay <= 0 {
53+
// Trigger a SQLITE_BUSY error
54+
return 0
55+
}
56+
}
57+
58+
time.Sleep(time.Duration(delay) * time.Millisecond)
59+
60+
// Attempt to access the database again
61+
return 1
62+
}
63+
64+
func main() {
65+
var simple bool
66+
flag.BoolVar(&simple, "simple", false, "Use the simple busy handler")
67+
flag.Parse()
68+
69+
sql.Register("sqlite3_with_busy_handler_example", &sqlite3.SQLiteDriver{
70+
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
71+
if simple {
72+
conn.RegisterBusyHandler(simpleBusyCallback)
73+
} else {
74+
conn.RegisterBusyHandler(defaultBusyCallback)
75+
}
76+
77+
return nil
78+
},
79+
})
80+
}

callback.go

+6
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ func preUpdateHookTrampoline(handle unsafe.Pointer, dbHandle uintptr, op int, db
9797
callback(data)
9898
}
9999

100+
//export busyHandlerTrampoline
101+
func busyHandlerTrampoline(handle unsafe.Pointer, count int) int {
102+
callback := lookupHandle(handle).(func(int) int)
103+
return callback(count)
104+
}
105+
100106
// Use handles to avoid passing Go pointers to C.
101107
type handleVal struct {
102108
db *SQLiteConn

sqlite3.go

+30
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64);
170170
171171
int authorizerTrampoline(void*, int, char*, char*, char*, char*);
172172
173+
int busyHandlerTrampoline(void*, int);
174+
173175
#ifdef SQLITE_LIMIT_WORKER_THREADS
174176
# define _SQLITE_HAS_LIMIT
175177
# define SQLITE_LIMIT_LENGTH 0
@@ -684,6 +686,34 @@ func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTe
684686
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(uintptr(pApp)), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
685687
}
686688

689+
// RegisterBusyHandler sets the busy handler for a connection.
690+
//
691+
// The parameter to the callback is the number of times that the busy
692+
// handler has been invoked previously for the same locking event.
693+
//
694+
// If there is an existing busy handler for this connection, it will be
695+
// removed. If callback is nil the existing handler (if any) will be removed
696+
// without creating a new one.
697+
//
698+
// If the busy callback returns 0 then no additional attempts are made to
699+
// access the database and SQLITE_BUSY is returned to the application.
700+
// If the callback returns non-zero, then another attempt is made to access
701+
// the database.
702+
func (c *SQLiteConn) RegisterBusyHandler(callback func(int) int) error {
703+
var rv C.int
704+
if callback == nil {
705+
rv = C.sqlite3_busy_handler(c.db, nil, nil)
706+
} else {
707+
handle := newHandle(c, callback)
708+
rv = C.sqlite3_busy_handler(c.db, (*[0]byte)(unsafe.Pointer(C.busyHandlerTrampoline)), handle)
709+
}
710+
if rv != C.SQLITE_OK {
711+
return c.lastError()
712+
}
713+
714+
return nil
715+
}
716+
687717
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
688718
//
689719
// Because aggregation is incremental, it's implemented in Go with a

static_mock.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ func (c *SQLiteConn) RegisterCommitHook(func() int) {
3636
func (c *SQLiteConn) RegisterFunc(string, any, bool) error { return errorMsg }
3737
func (c *SQLiteConn) RegisterRollbackHook(func()) {}
3838
func (c *SQLiteConn) RegisterUpdateHook(func(int, string, string, int64)) {}
39+
func (c *SQLiteConn) RegisterBusyHandler(func(int) int) error { return errorMsg }

0 commit comments

Comments
 (0)