diff --git a/go.mod b/go.mod index 8e12c4b91..8766becda 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/miekg/dns v1.1.62 github.com/minio/minio-go/v7 v7.0.81 github.com/mitchellh/mapstructure v1.5.0 - github.com/ncruces/go-sqlite3 v0.21.0 + github.com/ncruces/go-sqlite3 v0.21.1 github.com/oklog/ulid v1.3.1 github.com/prometheus/client_golang v1.20.5 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index 6c87de912..f3812e8ac 100644 --- a/go.sum +++ b/go.sum @@ -434,8 +434,8 @@ github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/ncruces/go-sqlite3 v0.21.0 h1:EwKFoy1hHEopN4sFZarmi+McXdbCcbTuLixhEayXVbQ= -github.com/ncruces/go-sqlite3 v0.21.0/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA= +github.com/ncruces/go-sqlite3 v0.21.1 h1:cbzIOY3jQrXZWVsBfH9TCFj/iqqMIcJ7PLye4AAEwoQ= +github.com/ncruces/go-sqlite3 v0.21.1/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= diff --git a/vendor/github.com/ncruces/go-sqlite3/conn.go b/vendor/github.com/ncruces/go-sqlite3/conn.go index 5ae0eef7c..d1ce30556 100644 --- a/vendor/github.com/ncruces/go-sqlite3/conn.go +++ b/vendor/github.com/ncruces/go-sqlite3/conn.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math" + "math/rand" "net/url" "strings" "time" @@ -24,7 +25,6 @@ type Conn struct { interrupt context.Context pending *Stmt stmts []*Stmt - timer *time.Timer busy func(context.Context, int) bool log func(xErrorCode, string) collation func(*Conn, string) @@ -36,7 +36,9 @@ type Conn struct { rollback func() arena arena - handle uint32 + busy1st time.Time + busylst time.Time + handle uint32 } // Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE] and [OPEN_URI]. @@ -389,38 +391,20 @@ func (c *Conn) BusyTimeout(timeout time.Duration) error { } func timeoutCallback(ctx context.Context, mod api.Module, count, tmout int32) (retry uint32) { + // https://fractaledmind.github.io/2024/04/15/sqlite-on-rails-the-how-and-why-of-optimal-performance/ if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.interrupt.Err() == nil { - const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64" - const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4" - const ndelay = int32(len(delays) - 1) - - var delay, prior int32 - if count <= ndelay { - delay = int32(delays[count]) - prior = int32(totals[count]) - } else { - delay = int32(delays[ndelay]) - prior = int32(totals[ndelay]) + delay*(count-ndelay) + switch { + case count == 0: + c.busy1st = time.Now() + case time.Since(c.busy1st) >= time.Duration(tmout)*time.Millisecond: + return 0 } - - if delay = min(delay, tmout-prior); delay > 0 { - delay := time.Duration(delay) * time.Millisecond - if c.interrupt.Done() == nil { - time.Sleep(delay) - return 1 - } - if c.timer == nil { - c.timer = time.NewTimer(delay) - } else { - c.timer.Reset(delay) - } - select { - case <-c.interrupt.Done(): - c.timer.Stop() - case <-c.timer.C: - return 1 - } + if time.Since(c.busylst) < time.Millisecond { + const sleepIncrement = 2*1024*1024 - 1 // power of two, ~2ms + time.Sleep(time.Duration(rand.Int63() & sleepIncrement)) } + c.busylst = time.Now() + return 1 } return 0 } diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go index 477e9a940..b9bb03bb7 100644 --- a/vendor/github.com/ncruces/go-sqlite3/driver/driver.go +++ b/vendor/github.com/ncruces/go-sqlite3/driver/driver.go @@ -379,7 +379,7 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e if err != nil { return nil, err } - if tail != "" { + if notWhitespace(tail) { s.Close() return nil, util.TailErr } diff --git a/vendor/github.com/ncruces/go-sqlite3/driver/whitespace.go b/vendor/github.com/ncruces/go-sqlite3/driver/whitespace.go new file mode 100644 index 000000000..8f45706f5 --- /dev/null +++ b/vendor/github.com/ncruces/go-sqlite3/driver/whitespace.go @@ -0,0 +1,61 @@ +package driver + +func notWhitespace(sql string) bool { + const ( + code = iota + slash + minus + ccomment + sqlcomment + endcomment + ) + + state := code + for _, b := range ([]byte)(sql) { + if b == 0 { + break + } + + switch state { + case code: + switch b { + case '/': + state = slash + case '-': + state = minus + case ' ', ';', '\t', '\n', '\v', '\f', '\r': + continue + default: + return true + } + case slash: + if b != '*' { + return true + } + state = ccomment + case minus: + if b != '-' { + return true + } + state = sqlcomment + case ccomment: + if b == '*' { + state = endcomment + } + case sqlcomment: + if b == '\n' { + state = code + } + case endcomment: + switch b { + case '/': + state = code + case '*': + state = endcomment + default: + state = ccomment + } + } + } + return state == slash || state == minus +} diff --git a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm index bf119d41d..05a6065f4 100644 Binary files a/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm and b/vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm differ diff --git a/vendor/github.com/ncruces/go-sqlite3/go.work.sum b/vendor/github.com/ncruces/go-sqlite3/go.work.sum index f41ac5535..ded9bda72 100644 --- a/vendor/github.com/ncruces/go-sqlite3/go.work.sum +++ b/vendor/github.com/ncruces/go-sqlite3/go.work.sum @@ -1,3 +1,4 @@ +github.com/ncruces/go-sqlite3 v0.21.0/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= diff --git a/vendor/github.com/ncruces/go-sqlite3/util/sql3util/parse/sql3parse_table.wasm b/vendor/github.com/ncruces/go-sqlite3/util/sql3util/parse/sql3parse_table.wasm index f0b3819c8..28eab8d29 100644 Binary files a/vendor/github.com/ncruces/go-sqlite3/util/sql3util/parse/sql3parse_table.wasm and b/vendor/github.com/ncruces/go-sqlite3/util/sql3util/parse/sql3parse_table.wasm differ diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go index 7d809b223..07de7c3d8 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go @@ -56,16 +56,12 @@ func osAllocate(file *os.File, size int64) error { return file.Truncate(size) } -func osUnlock(file *os.File, start, len int64) _ErrorCode { - err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{ - Type: unix.F_UNLCK, - Start: start, - Len: len, - }) - if err != nil { - return _IOERR_UNLOCK - } - return _OK +func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { + return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) +} + +func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { + return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) } func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { @@ -88,10 +84,14 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d return osLockErrorCode(err, def) } -func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { - return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) -} - -func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { - return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) +func osUnlock(file *os.File, start, len int64) _ErrorCode { + err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{ + Type: unix.F_UNLCK, + Start: start, + Len: len, + }) + if err != nil { + return _IOERR_UNLOCK + } + return _OK } diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go index f5841fc20..6199c7b00 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go @@ -3,7 +3,6 @@ package vfs import ( - "math/rand" "os" "time" @@ -22,6 +21,30 @@ func osAllocate(file *os.File, size int64) error { return unix.Fallocate(int(file.Fd()), 0, 0, size) } +func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { + return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) +} + +func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { + return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) +} + +func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { + lock := unix.Flock_t{ + Type: typ, + Start: start, + Len: len, + } + var err error + switch { + case timeout < 0: + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock) + default: + err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) + } + return osLockErrorCode(err, def) +} + func osUnlock(file *os.File, start, len int64) _ErrorCode { err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ Type: unix.F_UNLCK, @@ -33,40 +56,3 @@ func osUnlock(file *os.File, start, len int64) _ErrorCode { } return _OK } - -func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { - lock := unix.Flock_t{ - Type: typ, - Start: start, - Len: len, - } - var err error - switch { - case timeout == 0: - err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) - case timeout < 0: - err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock) - default: - before := time.Now() - for { - err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) - if errno, _ := err.(unix.Errno); errno != unix.EAGAIN { - break - } - if time.Since(before) > timeout { - break - } - const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms - time.Sleep(time.Duration(rand.Int63() & sleepIncrement)) - } - } - return osLockErrorCode(err, def) -} - -func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { - return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) -} - -func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { - return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) -} diff --git a/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go b/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go index 2872201b0..0398f4760 100644 --- a/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go +++ b/vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go @@ -45,6 +45,7 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) // Acquire the EXCLUSIVE lock. + // Can't wait here, because the file is not OVERLAPPED. rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) if rc != _OK { @@ -106,6 +107,27 @@ func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { return false, rc } +func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { + return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK) +} + +func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { + return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK) +} + +func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode { + var err error + switch { + case timeout == 0: + err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) + case timeout < 0: + err = osLockEx(file, flags, start, len) + default: + err = osLockExTimeout(file, flags, start, len, timeout) + } + return osLockErrorCode(err, def) +} + func osUnlock(file *os.File, start, len uint32) _ErrorCode { err := windows.UnlockFileEx(windows.Handle(file.Fd()), 0, len, 0, &windows.Overlapped{Offset: start}) @@ -118,52 +140,40 @@ func osUnlock(file *os.File, start, len uint32) _ErrorCode { return _OK } -func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode { - var err error - switch { - case timeout == 0: - err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len, 0) - case timeout < 0: - err = osLockEx(file, flags, start, len, 0) - default: - var event windows.Handle - event, err = windows.CreateEvent(nil, 1, 0, nil) - if err != nil { - break - } - defer windows.CloseHandle(event) - - err = osLockEx(file, flags, start, len, event) - if err == windows.ERROR_IO_PENDING { - rc, serr := windows.WaitForSingleObject(event, uint32(timeout/time.Millisecond)) - if rc == windows.WAIT_OBJECT_0 { - return _OK - } - if serr != nil { - err = serr - } else { - err = windows.Errno(rc) - } - windows.CancelIo(windows.Handle(file.Fd())) - } - } - return osLockErrorCode(err, def) -} - -func osLockEx(file *os.File, flags, start, len uint32, event windows.Handle) error { +func osLockEx(file *os.File, flags, start, len uint32) error { return windows.LockFileEx(windows.Handle(file.Fd()), flags, - 0, len, 0, &windows.Overlapped{ - Offset: start, - HEvent: event, - }) + 0, len, 0, &windows.Overlapped{Offset: start}) } -func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { - return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK) -} +func osLockExTimeout(file *os.File, flags, start, len uint32, timeout time.Duration) error { + event, err := windows.CreateEvent(nil, 1, 0, nil) + if err != nil { + return err + } + defer windows.CloseHandle(event) -func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { - return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK) + fd := windows.Handle(file.Fd()) + overlapped := &windows.Overlapped{ + Offset: start, + HEvent: event, + } + + err = windows.LockFileEx(fd, flags, 0, len, 0, overlapped) + if err != windows.ERROR_IO_PENDING { + return err + } + + ms := (timeout + time.Millisecond - 1) / time.Millisecond + rc, err := windows.WaitForSingleObject(event, uint32(ms)) + if rc == windows.WAIT_OBJECT_0 { + return nil + } + defer windows.CancelIoEx(fd, overlapped) + + if err != nil { + return err + } + return windows.Errno(rc) } func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { @@ -175,8 +185,8 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { switch errno { case windows.ERROR_LOCK_VIOLATION, - windows.ERROR_IO_PENDING, windows.ERROR_OPERATION_ABORTED, + windows.ERROR_IO_PENDING, windows.WAIT_TIMEOUT: return _BUSY } diff --git a/vendor/modules.txt b/vendor/modules.txt index 268e503ac..bffe1090e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -520,7 +520,7 @@ github.com/modern-go/reflect2 # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 ## explicit github.com/munnerz/goautoneg -# github.com/ncruces/go-sqlite3 v0.21.0 +# github.com/ncruces/go-sqlite3 v0.21.1 ## explicit; go 1.21 github.com/ncruces/go-sqlite3 github.com/ncruces/go-sqlite3/driver