Compare commits

..

9 Commits

Author SHA1 Message Date
Nuno Cruces
e08c7b3adf Refactor. 2024-12-19 15:14:20 +00:00
Nuno Cruces
66601dd3cb More BCE. 2024-12-19 14:00:46 +00:00
Nuno Cruces
58b66b75f1 Improved assertions. 2024-12-19 13:19:10 +00:00
Nuno Cruces
e0c6086aa9 Fix POSIX locks. 2024-12-18 16:21:24 +00:00
Nuno Cruces
9bc39c5b91 Remove dependency. 2024-12-17 15:43:19 +00:00
Nuno Cruces
12193cedea binaryen-version_121. 2024-12-17 15:25:25 +00:00
Nuno Cruces
71d95bf9d5 Fix #205. 2024-12-17 14:21:56 +00:00
Nuno Cruces
7e23100ff7 Help checklocks. 2024-12-16 13:47:59 +00:00
Nuno Cruces
e32d8401fb Improve Wal locking on BSD (#204) 2024-12-16 13:15:00 +00:00
31 changed files with 395 additions and 223 deletions

View File

@@ -3,13 +3,13 @@ set -euo pipefail
if [[ "$OSTYPE" == "linux"* ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_120_b/binaryen-version_120_b-x86_64-linux.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-x86_64-linux.tar.gz"
elif [[ "$OSTYPE" == "darwin"* ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-arm64-macos.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_120_b/binaryen-version_120_b-arm64-macos.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-arm64-macos.tar.gz"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-windows.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_120_b/binaryen-version_120_b-x86_64-windows.tar.gz"
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_121/binaryen-version_121-x86_64-windows.tar.gz"
fi
# Download tools

View File

@@ -18,8 +18,6 @@ jobs:
steps:
- uses: ilammy/msvc-dev-cmd@v1
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: stable }
- name: Build
shell: bash

View File

@@ -74,7 +74,7 @@ This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Test-matrix) on
Every commit is [tested](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix) on
Linux (amd64/arm64/386/riscv64/ppc64le/s390x), macOS (amd64/arm64),
Windows (amd64), FreeBSD (amd64), OpenBSD (amd64), NetBSD (amd64),
DragonFly BSD (amd64), illumos (amd64), and Solaris (amd64).

View File

@@ -274,6 +274,7 @@ func (n *connector) Connect(ctx context.Context) (res driver.Conn, err error) {
// if err != nil {
// log.Fatal(err)
// }
// defer conn.Close()
//
// err = conn.Raw(func(driverConn any) error {
// conn := driverConn.(driver.Conn)

View File

@@ -12,3 +12,63 @@ func namedValues(args []driver.Value) []driver.NamedValue {
}
return named
}
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
}

View File

@@ -1,9 +1,13 @@
package driver
import (
"context"
"database/sql/driver"
"reflect"
"testing"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func Test_namedValues(t *testing.T) {
@@ -16,3 +20,67 @@ func Test_namedValues(t *testing.T) {
t.Errorf("got %v, want %v", got, want)
}
}
func Fuzz_notWhitespace(f *testing.F) {
f.Add("")
f.Add(" ")
f.Add(";")
f.Add("0")
f.Add("-")
f.Add("-0")
f.Add("--")
f.Add("--0")
f.Add("--\n")
f.Add("--0\n")
f.Add("/0")
f.Add("/*")
f.Add("/*/")
f.Add("/**")
f.Add("/*0")
f.Add("/**/")
f.Add("/***/")
f.Add("/**0/")
f.Add("\v")
f.Add(" \v")
f.Add("\xf0")
f.Add("\000")
db, err := Open(":memory:")
if err != nil {
f.Fatal(err)
}
defer db.Close()
f.Fuzz(func(t *testing.T, str string) {
if len(str) > 128 {
t.SkipNow()
}
c, err := db.Conn(context.Background())
if err != nil {
t.Fatal(err)
}
defer c.Close()
c.Raw(func(driverConn any) error {
conn := driverConn.(*conn).Conn
stmt, tail, err := conn.Prepare(str)
stmt.Close()
// It's hard to be bug for bug compatible with SQLite.
// We settle for somewhat less:
// - if SQLite reports whitespace, we must too
// - if we report whitespace, SQLite must not parse a statement
if notWhitespace(str) {
if stmt == nil && tail == "" && err == nil {
t.Errorf("was whitespace: %q", str)
}
} else {
if stmt != nil {
t.Errorf("was not whitespace: %q (%v)", str, err)
}
}
return nil
})
})
}

View File

@@ -1,61 +0,0 @@
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
}

View File

@@ -1,73 +0,0 @@
package driver
import (
"context"
"testing"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func Fuzz_notWhitespace(f *testing.F) {
f.Add("")
f.Add(" ")
f.Add(";")
f.Add("0")
f.Add("-")
f.Add("-0")
f.Add("--")
f.Add("--0")
f.Add("--\n")
f.Add("--0\n")
f.Add("/0")
f.Add("/*")
f.Add("/*/")
f.Add("/**")
f.Add("/*0")
f.Add("/**/")
f.Add("/***/")
f.Add("/**0/")
f.Add("\v")
f.Add(" \v")
f.Add("\xf0")
f.Add("\000")
db, err := Open(":memory:")
if err != nil {
f.Fatal(err)
}
defer db.Close()
f.Fuzz(func(t *testing.T, str string) {
if len(str) > 128 {
t.SkipNow()
}
c, err := db.Conn(context.Background())
if err != nil {
t.Fatal(err)
}
defer c.Close()
c.Raw(func(driverConn any) error {
conn := driverConn.(*conn).Conn
stmt, tail, err := conn.Prepare(str)
stmt.Close()
// It's hard to be bug for bug compatible with SQLite.
// We settle for somewhat less:
// - if SQLite reports whitespace, we must too
// - if we report whitespace, SQLite must not parse a statement
if notWhitespace(str) {
if stmt == nil && tail == "" && err == nil {
t.Errorf("was whitespace: %q", str)
}
} else {
if stmt != nil {
t.Errorf("was not whitespace: %q (%v)", str, err)
}
}
return nil
})
})
}

Binary file not shown.

View File

@@ -32,6 +32,11 @@ func Test_bcw2(t *testing.T) {
t.Fatal(err)
}
_, err = tx.Exec(`DELETE FROM test LIMIT 1`)
if err != nil {
t.Fatal(err)
}
err = tx.Commit()
if err != nil {
t.Fatal(err)

View File

@@ -13,15 +13,14 @@ mkdir -p build/ext/
cp "$ROOT"/sqlite3/*.[ch] build/
cp "$ROOT"/sqlite3/*.patch build/
# https://sqlite.org/src/info/08cfa7e8b3090151
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=08cfa7e8 | tar xz
# https://sqlite.org/src/info/ec5d7025cba9f4ac
curl -# https://sqlite.org/src/tarball/sqlite.tar.gz?r=ec5d7025 | tar xz
cd sqlite
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT
else
sh configure
make sqlite3.c
sh configure --enable-update-limit && make sqlite3.c
fi
cd ~-
@@ -54,6 +53,7 @@ cd ~-
-Wl,--import-undefined \
-Wl,--initial-memory=327680 \
-D_HAVE_SQLITE_CONFIG_H \
-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT \
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
$(awk '{print "-Wl,--export="$0}' ../exports.txt)

Binary file not shown.

View File

@@ -44,6 +44,8 @@ func Register(db *sqlite3.Conn) error {
type OpenCallback func(*sqlite3.Blob, ...sqlite3.Value) error
func readblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
_ = arg[5] // bounds check
blob, err := getAuxBlob(ctx, arg, false)
if err != nil {
ctx.ResultError(err)
@@ -78,6 +80,8 @@ func readblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
}
func writeblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
_ = arg[5] // bounds check
blob, err := getAuxBlob(ctx, arg, true)
if err != nil {
ctx.ResultError(err)

View File

@@ -76,6 +76,7 @@ func load(ctx sqlite3.Context, i int, expr string) (*regexp.Regexp, error) {
}
func regex(ctx sqlite3.Context, arg ...sqlite3.Value) {
_ = arg[1] // bounds check
re, err := load(ctx, 0, arg[0].Text())
if err != nil {
ctx.ResultError(err)
@@ -165,6 +166,8 @@ func regexInstr(ctx sqlite3.Context, arg ...sqlite3.Value) {
}
func regexReplace(ctx sqlite3.Context, arg ...sqlite3.Value) {
_ = arg[2] // bounds check
re, err := load(ctx, 1, arg[1].Text())
if err != nil {
ctx.ResultError(err)

View File

@@ -189,6 +189,7 @@ func like(ctx sqlite3.Context, arg ...sqlite3.Value) {
return
}
}
_ = arg[1] // bounds check
type likeData struct {
*regexp.Regexp

29
internal/dotlk/dotlk.go Normal file
View File

@@ -0,0 +1,29 @@
package dotlk
import (
"errors"
"io/fs"
"os"
)
// LockShm creates a directory on disk to prevent SQLite
// from using this path for a shared memory file.
func LockShm(name string) error {
err := os.Mkdir(name, 0777)
if errors.Is(err, fs.ErrExist) {
s, err := os.Lstat(name)
if err == nil && s.IsDir() {
return nil
}
}
return err
}
// Unlock removes the lock or shared memory file.
func Unlock(name string) error {
err := os.Remove(name)
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}

View File

@@ -0,0 +1,13 @@
//go:build !unix
package dotlk
import "os"
// TryLock returns nil if it acquired the lock,
// fs.ErrExist if another process has the lock.
func TryLock(name string) error {
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
f.Close()
return err
}

View File

@@ -0,0 +1,50 @@
//go:build unix
package dotlk
import (
"errors"
"io/fs"
"os"
"strconv"
"golang.org/x/sys/unix"
)
// TryLock returns nil if it acquired the lock,
// fs.ErrExist if another process has the lock.
func TryLock(name string) error {
for retry := true; retry; retry = false {
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err == nil {
f.WriteString(strconv.Itoa(os.Getpid()))
f.Close()
return nil
}
if !errors.Is(err, fs.ErrExist) {
return err
}
if !removeStale(name) {
break
}
}
return fs.ErrExist
}
func removeStale(name string) bool {
buf, err := os.ReadFile(name)
if err != nil {
return errors.Is(err, fs.ErrNotExist)
}
pid, err := strconv.Atoi(string(buf))
if err != nil {
return false
}
if unix.Kill(pid, 0) == nil {
return false
}
err = os.Remove(name)
return err == nil || errors.Is(err, fs.ErrNotExist)
}

View File

@@ -39,13 +39,13 @@ func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *Mapped
// Save the newly allocated region.
ptr := uint32(stack[0])
buf := View(mod, ptr, uint64(size))
addr := unsafe.Pointer(&buf[0])
s.regions = append(s.regions, &MappedRegion{
res := &MappedRegion{
Ptr: ptr,
addr: addr,
size: size,
})
return s.regions[len(s.regions)-1]
addr: unsafe.Pointer(&buf[0]),
}
s.regions = append(s.regions, res)
return res
}
type MappedRegion struct {

View File

@@ -265,10 +265,11 @@ func (a *arena) mark() (reset func()) {
ptrs := len(a.ptrs)
next := a.next
return func() {
for _, ptr := range a.ptrs[ptrs:] {
rest := a.ptrs[ptrs:]
for _, ptr := range a.ptrs[:ptrs] {
a.sqlt.free(ptr)
}
a.ptrs = a.ptrs[:ptrs]
a.ptrs = rest
a.next = next
}
}

View File

@@ -48,11 +48,6 @@ On Unix, this package may use `mmap` to implement
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
like SQLite.
With [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2)
a WAL database can only be accessed by a single proccess.
Other processes that attempt to access a database locked with BSD locks,
will fail with the [`SQLITE_PROTOCOL`](https://sqlite.org/rescode.html#protocol) error code.
On Windows, this package may use `MapViewOfFile`, like SQLite.
You can also opt into a cross-platform, in-process, memory sharing implementation

View File

@@ -20,12 +20,10 @@ const (
)
func (f *vfsFile) Lock(lock LockLevel) error {
// Argument check. SQLite never explicitly requests a pending lock.
if lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
switch {
case lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE:
// Argument check. SQLite never explicitly requests a pending lock.
panic(util.AssertErr())
case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE:
// Connection state check.
panic(util.AssertErr())
@@ -87,13 +85,12 @@ func (f *vfsFile) Lock(lock LockLevel) error {
}
func (f *vfsFile) Unlock(lock LockLevel) error {
// Argument check.
if lock != LOCK_NONE && lock != LOCK_SHARED {
switch {
case lock != LOCK_NONE && lock != LOCK_SHARED:
// Argument check.
panic(util.AssertErr())
}
// Connection state check.
if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE {
case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE:
// Connection state check.
panic(util.AssertErr())
}

View File

@@ -9,11 +9,11 @@ import (
)
func osGetSharedLock(file *os.File) _ErrorCode {
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
return osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
}
func osGetReservedLock(file *os.File) _ErrorCode {
rc := osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
rc := osFlock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
if rc == _BUSY {
// The documentation states that a lock is upgraded by
// releasing the previous lock, then acquiring the new lock.
@@ -37,7 +37,7 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode {
}
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
rc := osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
rc := osFlock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
if rc == _BUSY {
// The documentation states that a lock is downgraded by
// releasing the previous lock then acquiring the new lock.
@@ -66,7 +66,36 @@ func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
return lock == unix.F_WRLCK, rc
}
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
func osFlock(file *os.File, how int, def _ErrorCode) _ErrorCode {
err := unix.Flock(int(file.Fd()), how)
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, start, len int64) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, _IOERR_LOCK)
}
func osLock(file *os.File, typ int16, start, len int64, def _ErrorCode) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
Type: typ,
Start: start,
Len: len,
})
return osLockErrorCode(err, def)
}
func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return _IOERR_UNLOCK
}
return _OK
}

View File

@@ -7,6 +7,8 @@ import (
"io/fs"
"os"
"sync"
"github.com/ncruces/go-sqlite3/internal/dotlk"
)
var (
@@ -28,12 +30,10 @@ func osGetSharedLock(file *os.File) _ErrorCode {
name := file.Name()
locker := vfsDotLocks[name]
if locker == nil {
f, err := os.OpenFile(name+".lock", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
f.Close()
if errors.Is(err, fs.ErrExist) {
return _BUSY // Another process has the lock.
}
if err != nil {
if err := dotlk.TryLock(name + ".lock"); err != nil {
if errors.Is(err, fs.ErrExist) {
return _BUSY // Another process has the lock.
}
return _IOERR_LOCK
}
locker = &vfsDotLocker{}
@@ -114,8 +114,7 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
}
if locker.shared == 1 {
err := os.Remove(name + ".lock")
if err != nil && !errors.Is(err, fs.ErrNotExist) {
if err := dotlk.Unlock(name + ".lock"); err != nil {
return _IOERR_UNLOCK
}
delete(vfsDotLocks, name)

View File

@@ -4,7 +4,9 @@ package vfs
import (
"context"
"errors"
"io"
"io/fs"
"os"
"sync"
@@ -20,7 +22,7 @@ type vfsShmParent struct {
refs int // +checklocks:vfsShmListMtx
lock [_SHM_NLOCK]int16 // +checklocks:Mutex
lock [_SHM_NLOCK]int8 // +checklocks:Mutex
sync.Mutex
}
@@ -71,23 +73,21 @@ func (s *vfsShm) shmOpen() _ErrorCode {
return _OK
}
// Always open file read-write, as it will be shared.
f, err := os.OpenFile(s.path,
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
if err != nil {
return _CANTOPEN
}
// Closes file if it's not nil.
var f *os.File
// Close file on error.
// Keep this here to avoid confusing checklocks.
defer func() { f.Close() }()
fi, err := f.Stat()
if err != nil {
return _IOERR_FSTAT
}
vfsShmListMtx.Lock()
defer vfsShmListMtx.Unlock()
// Stat file without opening it.
// Closing it would release all POSIX locks on it.
fi, err := os.Stat(s.path)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return _IOERR_FSTAT
}
// Find a shared file, increase the reference count.
for _, g := range vfsShmList {
if g != nil && os.SameFile(fi, g.info) {
@@ -97,13 +97,33 @@ func (s *vfsShm) shmOpen() _ErrorCode {
}
}
// Lock and truncate the file.
// The lock is only released by closing the file.
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK {
// Always open file read-write, as it will be shared.
f, err = os.OpenFile(s.path,
os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666)
if err != nil {
return _CANTOPEN
}
// Dead man's switch.
if lock, rc := osTestLock(f, _SHM_DMS, 1); rc != _OK {
return _IOERR_LOCK
} else if lock == unix.F_WRLCK {
return _BUSY
} else if lock == unix.F_UNLCK {
if rc := osWriteLock(f, _SHM_DMS, 1); rc != _OK {
return rc
}
if err := f.Truncate(0); err != nil {
return _IOERR_SHMOPEN
}
}
if rc := osReadLock(f, _SHM_DMS, 1); rc != _OK {
return rc
}
if err := f.Truncate(0); err != nil {
return _IOERR_SHMOPEN
fi, err = f.Stat()
if err != nil {
return _IOERR_FSTAT
}
// Add the new shared file.
@@ -157,7 +177,42 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
s.Lock()
defer s.Unlock()
return s.shmMemLock(offset, n, flags)
// Check if we could obtain/release the lock locally.
rc := s.shmMemLock(offset, n, flags)
if rc != _OK {
return rc
}
// Obtain/release the appropriate file locks.
switch {
case flags&_SHM_UNLOCK != 0:
begin, end := offset, offset+n
for i := begin; i < end; i++ {
if s.vfsShmParent.lock[i] != 0 {
if i > begin {
rc |= osUnlock(s.File, _SHM_BASE+int64(begin), int64(i-begin))
}
begin = i + 1
}
}
if end > begin {
rc |= osUnlock(s.File, _SHM_BASE+int64(begin), int64(end-begin))
}
return rc
case flags&_SHM_SHARED != 0:
rc = osReadLock(s.File, _SHM_BASE+int64(offset), int64(n))
case flags&_SHM_EXCLUSIVE != 0:
rc = osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n))
default:
panic(util.AssertErr())
}
// Release the local lock we had acquired.
if rc != _OK {
s.shmMemLock(offset, n, flags^(_SHM_UNLOCK|_SHM_LOCK))
}
return rc
}
func (s *vfsShm) shmUnmap(delete bool) {

View File

@@ -6,11 +6,11 @@ import (
"context"
"errors"
"io/fs"
"os"
"sync"
"github.com/tetratelabs/wazero/api"
"github.com/ncruces/go-sqlite3/internal/dotlk"
"github.com/ncruces/go-sqlite3/internal/util"
)
@@ -18,7 +18,7 @@ type vfsShmParent struct {
shared [][_WALINDEX_PGSZ]byte
refs int // +checklocks:vfsShmListMtx
lock [_SHM_NLOCK]int16 // +checklocks:Mutex
lock [_SHM_NLOCK]int8 // +checklocks:Mutex
sync.Mutex
}
@@ -58,8 +58,7 @@ func (s *vfsShm) Close() error {
return nil
}
err := os.Remove(s.path)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
if err := dotlk.Unlock(s.path); err != nil {
return _IOERR_UNLOCK
}
delete(vfsShmList, s.path)
@@ -82,9 +81,8 @@ func (s *vfsShm) shmOpen() _ErrorCode {
return _OK
}
// Create a directory on disk to ensure only this process
// uses this path to register a shared memory.
err := os.Mkdir(s.path, 0777)
// Dead man's switch.
err := dotlk.LockShm(s.path)
if errors.Is(err, fs.ErrExist) {
return _BUSY
}

View File

@@ -10,9 +10,6 @@ func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode {
case flags&_SHM_UNLOCK != 0:
for i := offset; i < offset+n; i++ {
if s.lock[i] {
if s.vfsShmParent.lock[i] == 0 {
panic(util.AssertErr())
}
if s.vfsShmParent.lock[i] <= 0 {
s.vfsShmParent.lock[i] = 0
} else {
@@ -23,20 +20,21 @@ func (s *vfsShm) shmMemLock(offset, n int32, flags _ShmFlag) _ErrorCode {
}
case flags&_SHM_SHARED != 0:
for i := offset; i < offset+n; i++ {
if s.lock[i] {
panic(util.AssertErr())
}
if s.vfsShmParent.lock[i]+1 <= 0 {
if !s.lock[i] &&
s.vfsShmParent.lock[i]+1 <= 0 {
return _BUSY
}
}
for i := offset; i < offset+n; i++ {
s.vfsShmParent.lock[i]++
s.lock[i] = true
if !s.lock[i] {
s.vfsShmParent.lock[i]++
s.lock[i] = true
}
}
case flags&_SHM_EXCLUSIVE != 0:
for i := offset; i < offset+n; i++ {
if s.lock[i] {
// SQLite never requests an exclusive lock that it already holds.
panic(util.AssertErr())
}
if s.vfsShmParent.lock[i] != 0 {

View File

@@ -110,7 +110,12 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
// Argument check.
if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK {
switch {
case n <= 0:
panic(util.AssertErr())
case offset < 0 || offset+n > _SHM_NLOCK:
panic(util.AssertErr())
case n != 1 && flags&_SHM_EXCLUSIVE == 0:
panic(util.AssertErr())
}
switch flags {
@@ -123,9 +128,6 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
default:
panic(util.AssertErr())
}
if n != 1 && flags&_SHM_EXCLUSIVE == 0 {
panic(util.AssertErr())
}
var timeout time.Duration
if s.blocking {

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:32dfb1b76b5cf71cb7610a7cb34ec3d9b68fcea5e54d734e35ae6b649c923c7a
size 477479
oid sha256:e20f37d94223a88d8f94b3a20177c0fbf53392df2f9c59a28cc7f1f2b5d3de81
size 477370

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a0d0b42fe3120de55da71dae0b2aa6ae5b4888593d2129e651590f12555ecdf3
size 491151
oid sha256:eebe395695c739a24e9cded13553b97d232eb268a5bc36f10f27cc13945e78cd
size 491003