Skip to content

Commit ed932ee

Browse files
committed
Interrupt busy handlers.
1 parent 3d30a56 commit ed932ee

File tree

11 files changed

+113
-11
lines changed

11 files changed

+113
-11
lines changed

conn.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,9 @@ func (c *Conn) checkInterrupt() {
346346
}
347347

348348
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
349-
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.interrupt != nil {
350-
if c.interrupt.Err() != nil {
351-
interrupt = 1
352-
}
349+
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB &&
350+
c.interrupt != nil && c.interrupt.Err() != nil {
351+
interrupt = 1
353352
}
354353
return interrupt
355354
}
@@ -363,6 +362,30 @@ func (c *Conn) BusyTimeout(timeout time.Duration) error {
363362
return c.error(r)
364363
}
365364

365+
func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmout int32) (retry uint32) {
366+
if c, ok := ctx.Value(connKey{}).(*Conn); ok &&
367+
(c.interrupt == nil || c.interrupt.Err() == nil) {
368+
const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64"
369+
const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4"
370+
const ndelay = int32(len(delays) - 1)
371+
372+
var delay, prior int32
373+
if count <= ndelay {
374+
delay = int32(delays[count])
375+
prior = int32(totals[count])
376+
} else {
377+
delay = int32(delays[ndelay])
378+
prior = int32(totals[ndelay]) + delay*(count-ndelay)
379+
}
380+
381+
if delay = min(delay, tmout-prior); delay > 0 {
382+
time.Sleep(time.Duration(delay) * time.Millisecond)
383+
retry = 1
384+
}
385+
}
386+
return retry
387+
}
388+
366389
// BusyHandler registers a callback to handle [BUSY] errors.
367390
//
368391
// https://sqlite.org/c3ref/busy_handler.html
@@ -380,7 +403,8 @@ func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
380403
}
381404

382405
func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) {
383-
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
406+
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil &&
407+
(c.interrupt == nil || c.interrupt.Err() == nil) {
384408
if c.busy(int(count)) {
385409
retry = 1
386410
}

embed/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ BINARYEN="$ROOT/tools/binaryen-version_117/bin"
88
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin"
99

1010
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
11-
-Wall -Wextra -Wno-unused-parameter \
11+
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
1212
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
1313
-I"$ROOT/sqlite3" \
1414
-mexec-model=reactor \

embed/sqlite3.wasm

-87 Bytes
Binary file not shown.

sqlite.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,9 @@ func (a *arena) string(s string) uint32 {
296296
}
297297

298298
func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
299-
util.ExportFuncIII(env, "go_busy_handler", busyCallback)
300299
util.ExportFuncII(env, "go_progress_handler", progressCallback)
300+
util.ExportFuncIIII(env, "go_busy_timeout", timeoutCallback)
301+
util.ExportFuncIII(env, "go_busy_handler", busyCallback)
301302
util.ExportFuncII(env, "go_commit_hook", commitCallback)
302303
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
303304
util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback)

sqlite3/busy_timeout.patch

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Replace sqliteDefaultBusyCallback.
2+
# This patch allows Go to handle (and interrupt) sqlite3_busy_timeout.
3+
--- sqlite3.c.orig
4+
+++ sqlite3.c
5+
@@ -181581,7 +181581,7 @@
6+
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
7+
#endif
8+
if( ms>0 ){
9+
- sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback,
10+
+ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteBusyCallback,
11+
(void*)db);
12+
db->busyTimeout = ms;
13+
}else{

sqlite3/hooks.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
int go_progress_handler(void *);
66
int go_busy_handler(void *, int);
7+
int go_busy_timeout(void *, int count, int tmout);
78

89
int go_commit_hook(void *);
910
void go_rollback_hook(void *);
@@ -55,4 +56,12 @@ int sqlite3_autovacuum_pages_go(sqlite3 *db, go_handle app) {
5556
int rc = sqlite3_autovacuum_pages(db, go_autovacuum_pages, app, go_destroy);
5657
if (rc) go_destroy(app);
5758
return rc;
58-
}
59+
}
60+
61+
#ifndef sqliteBusyCallback
62+
63+
static int sqliteBusyCallback(sqlite3 *db, int count) {
64+
return go_busy_timeout(db, count, db->busyTimeout);
65+
}
66+
67+
#endif

sqlite3/sqlite_cfg.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@
3535

3636
// Because Wasm does not support shared memory,
3737
// SQLite disables WAL for Wasm builds.
38-
// But we want it.
3938
#undef SQLITE_OMIT_WAL
4039

4140
// Implemented in vfs.c.
42-
int localtime_s(struct tm *const pTm, time_t const *const pTime);
41+
int localtime_s(struct tm *const pTm, time_t const *const pTime);
42+
43+
// Implemented in hooks.c.
44+
#ifndef sqliteBusyCallback
45+
static int sqliteBusyCallback(sqlite3 *, int);
46+
#endif

sqlite3/vfs_find.patch

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Wrap sqlite3_vfs_find.
2+
# This patch allows Go VFSes to be (un)registered.
23
--- sqlite3.c.orig
34
+++ sqlite3.c
4-
@@ -26089,7 +26089,7 @@
5+
@@ -26372,7 +26372,7 @@
56
** Locate a VFS by name. If no name is given, simply return the
67
** first VFS on the list.
78
*/

tests/txn_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/ncruces/go-sqlite3"
99
_ "github.com/ncruces/go-sqlite3/embed"
1010
_ "github.com/ncruces/go-sqlite3/tests/testcfg"
11+
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
1112
)
1213

1314
func TestConn_Transaction_exec(t *testing.T) {
@@ -247,6 +248,51 @@ func TestConn_Transaction_interrupted(t *testing.T) {
247248
}
248249
}
249250

251+
func TestConn_Transaction_busy(t *testing.T) {
252+
t.Parallel()
253+
254+
db1, err := sqlite3.Open("file:/test.db?vfs=memdb")
255+
if err != nil {
256+
t.Fatal(err)
257+
}
258+
defer db1.Close()
259+
260+
db2, err := sqlite3.Open("file:/test.db?vfs=memdb&_pragma=busy_timeout(10000)")
261+
if err != nil {
262+
t.Fatal(err)
263+
}
264+
defer db2.Close()
265+
266+
err = db1.Exec(`CREATE TABLE test (col)`)
267+
if err != nil {
268+
t.Fatal(err)
269+
}
270+
271+
tx, err := db1.BeginImmediate()
272+
if err != nil {
273+
t.Fatal(err)
274+
}
275+
err = db1.Exec(`INSERT INTO test VALUES (1)`)
276+
if err != nil {
277+
t.Fatal(err)
278+
}
279+
280+
ctx, cancel := context.WithCancel(context.Background())
281+
db2.SetInterrupt(ctx)
282+
go cancel()
283+
284+
_, err = db2.BeginExclusive()
285+
if !errors.Is(err, sqlite3.BUSY) {
286+
t.Errorf("got %v, want sqlite3.BUSY", err)
287+
}
288+
289+
err = nil
290+
tx.End(&err)
291+
if err != nil {
292+
t.Fatal(err)
293+
}
294+
}
295+
250296
func TestConn_Transaction_rollback(t *testing.T) {
251297
t.Parallel()
252298

vfs/tests/mptest/testdata/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include <unistd.h>
22

3+
#define sqliteBusyCallback sqliteDefaultBusyCallback
4+
35
// Amalgamation
46
#include "sqlite3.c"
57
// VFS

0 commit comments

Comments
 (0)