Compare commits

..

16 Commits

Author SHA1 Message Date
Nuno Cruces
e2a2d447ce Updated binaries. 2024-09-04 19:38:10 +01:00
Nuno Cruces
75190a6f98 os.Executable rather than os.Args[0] 2024-09-04 18:48:42 +01:00
Nuno Cruces
35c5619880 Tweak. 2024-09-04 18:26:57 +01:00
Nuno Cruces
b51234cc82 Reduce allocs. 2024-09-03 17:32:06 +01:00
Nuno Cruces
cf7b89d3c4 Issue #145. 2024-09-03 12:24:03 +01:00
Nuno Cruces
ff9f27a778 Fix #141. 2024-09-03 11:22:24 +01:00
Nuno Cruces
f26f1a17a9 Blocking locks (#144) 2024-09-02 23:59:26 +01:00
Nuno Cruces
b9b2ff13da Stricter test. 2024-08-30 09:32:30 +01:00
Nuno Cruces
78473b4b37 Fix BSD locks. 2024-08-30 01:27:57 +01:00
Nuno Cruces
3806c1cc23 Test tweaks. 2024-08-30 01:27:22 +01:00
Nuno Cruces
1660c41f8c Typo. 2024-08-29 10:32:10 +01:00
Nuno Cruces
62b67c937e Tweak. 2024-08-27 01:55:39 +01:00
Nuno Cruces
9e9971c292 Fix enconding. 2024-08-27 01:45:44 +01:00
Nuno Cruces
d13bf1afaa Readability. 2024-08-26 19:47:46 +01:00
Nuno Cruces
f7c9551d66 Update README.md 2024-08-15 12:42:38 +01:00
Nuno Cruces
22beef91d2 Updated dependencies. 2024-08-14 17:56:06 +01:00
30 changed files with 199 additions and 65 deletions

View File

@@ -12,6 +12,20 @@ It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite,
and uses [wazero](https://wazero.io/) as the runtime.\
Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies [^1].
### Getting started
Using the [`database/sql`](https://pkg.go.dev/database/sql) driver:
```go
import "database/sql"
import _ "github.com/ncruces/go-sqlite3/driver"
import _ "github.com/ncruces/go-sqlite3/embed"
var version string
db, _ := sql.Open("sqlite3", "file:demo.db")
db.QueryRow(`SELECT sqlite_version()`).Scan(&version)
```
### Packages
- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)

25
conn.go
View File

@@ -23,6 +23,7 @@ type Conn struct {
interrupt context.Context
pending *Stmt
stmts []*Stmt
timer *time.Timer
busy func(int) bool
log func(xErrorCode, string)
collation func(*Conn, string)
@@ -389,11 +390,25 @@ func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmo
}
if delay = min(delay, tmout-prior); delay > 0 {
time.Sleep(time.Duration(delay) * time.Millisecond)
retry = 1
delay := time.Duration(delay) * time.Millisecond
if c.interrupt == nil || 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
}
}
}
return retry
return 0
}
// BusyHandler registers a callback to handle [BUSY] errors.
@@ -492,9 +507,7 @@ func (c *Conn) stmtsIter(yield func(*Stmt) bool) {
// DriverConn is implemented by the SQLite [database/sql] driver connection.
//
// It can be used to access SQLite features like [online backup].
//
// [online backup]: https://sqlite.org/backup.html
// Deprecated: use [github.com/ncruces/go-sqlite3/driver.Conn] instead.
type DriverConn interface {
Raw() *Conn
}

View File

@@ -130,6 +130,11 @@ type SQLite struct {
term func(*sqlite3.Conn) error
}
var (
// Ensure these interfaces are implemented:
_ driver.DriverContext = &SQLite{}
)
// Open implements [database/sql/driver.Driver].
func (d *SQLite) Open(name string) (driver.Conn, error) {
c, err := d.newConnector(name)
@@ -255,6 +260,35 @@ func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
return c, nil
}
// Conn is implemented by the SQLite [database/sql] driver connections.
//
// It can be used to access SQLite features like [online backup]:
//
// db, err := driver.Open("temp.db")
// if err != nil {
// log.Fatal(err)
// }
// defer db.Close()
//
// conn, err := db.Conn(context.TODO())
// if err != nil {
// log.Fatal(err)
// }
//
// err = conn.Raw(func(driverConn any) error {
// conn := driverConn.(driver.Conn)
// return conn.Raw().Backup("main", "backup.db")
// })
// if err != nil {
// log.Fatal(err)
// }
//
// [online backup]: https://sqlite.org/backup.html
type Conn interface {
Raw() *sqlite3.Conn
driver.Conn
}
type conn struct {
*sqlite3.Conn
txLock string
@@ -266,10 +300,10 @@ type conn struct {
var (
// Ensure these interfaces are implemented:
_ Conn = &conn{}
_ driver.ConnBeginTx = &conn{}
_ driver.ConnPrepareContext = &conn{}
_ driver.ExecerContext = &conn{}
_ driver.ConnBeginTx = &conn{}
_ sqlite3.DriverConn = &conn{}
)
func (c *conn) Raw() *sqlite3.Conn {

View File

@@ -176,7 +176,7 @@ func Example_customTime() {
// a time. 2009-11-17T20:34:58.650Z goes in, but parsing and formatting
// it with [time.RFC3338Nano] results in 2009-11-17T20:34:58.65Z. Though
// the times are identical, the trailing zero is lost in the string
// representation so the driver considers the conversion unsuccesful.
// representation so the driver considers the conversion unsuccessful.
c1 := CustomTime{time.Date(
2009, 11, 17, 20, 34, 58, 650000000, time.UTC)}

Binary file not shown.

Binary file not shown.

View File

@@ -58,9 +58,10 @@ func Example() {
// Hello BLOB!
}
func init() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(blobio.Register)
sqlite3.AutoExtension(array.Register)
m.Run()
}
func Test_readblob(t *testing.T) {

View File

@@ -12,8 +12,9 @@ import (
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func init() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(bloom.Register)
m.Run()
}
func TestRegister(t *testing.T) {

View File

@@ -54,8 +54,9 @@ func Example() {
// On Twosday, 1€ = $1.1342
}
func init() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(csv.Register)
m.Run()
}
func TestRegister(t *testing.T) {

View File

@@ -83,8 +83,9 @@ func Example() {
// gamma 80 75 78 80
}
func init() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(pivot.Register)
m.Run()
}
func TestRegister(t *testing.T) {

View File

@@ -48,8 +48,9 @@ func Example() {
// Twosday was 2022-2-22
}
func init() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(statement.Register)
m.Run()
}
func TestRegister(t *testing.T) {

View File

@@ -32,7 +32,7 @@ func (q *percentile) Step(ctx sqlite3.Context, arg ...sqlite3.Value) {
q.nums = append(q.nums, a.Float())
}
if q.kind != median {
q.arg1 = arg[1].Blob(q.arg1[:0])
q.arg1 = append(q.arg1[:0], arg[1].RawText()...)
}
}
@@ -79,7 +79,7 @@ func getPercentile(nums []float64, pos float64, disc bool) (float64, error) {
}
m1 := slices.Min(nums[int(i)+1:])
return math.FMA(f, m1, -math.FMA(f, m0, -m0)), nil
return math.FMA(f, m1, math.FMA(-f, m0, m0)), nil
}
func getPercentiles(nums []float64, pos []float64, disc bool) error {

View File

@@ -10,8 +10,9 @@ import (
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
)
func init() {
func TestMain(m *testing.M) {
sqlite3.AutoExtension(stats.Register)
m.Run()
}
func TestRegister_variance(t *testing.T) {

View File

@@ -66,23 +66,23 @@ func generate(ctx sqlite3.Context, arg ...sqlite3.Value) {
domain = uuid.Domain(arg[1].Int64())
if domain == 0 {
if txt := arg[1].RawText(); len(txt) > 0 {
switch txt[0] | 0x20 {
switch txt[0] | 0x20 { // to lower
case 'g': // group
domain = 1
domain = uuid.Group
case 'o': // org
domain = 2
domain = uuid.Org
}
}
}
}
if len(arg) > 2 {
id := uint32(arg[2].Int64())
u, err = uuid.NewDCESecurity(domain, id)
} else if domain == uuid.Person {
switch {
case len(arg) > 2:
u, err = uuid.NewDCESecurity(domain, uint32(arg[2].Int64()))
case domain == uuid.Person:
u, err = uuid.NewDCEPerson()
} else if domain == uuid.Group {
case domain == uuid.Group:
u, err = uuid.NewDCEGroup()
} else {
default:
err = util.ErrorString("missing id")
}

View File

@@ -78,7 +78,7 @@ func (f *countASCII) isASCII(arg sqlite3.Value) bool {
if arg.Type() != sqlite3.TEXT {
return false
}
for _, c := range arg.RawBlob() {
for _, c := range arg.RawText() {
if c > unicode.MaxASCII {
return false
}

4
go.mod
View File

@@ -12,8 +12,8 @@ require (
github.com/tetratelabs/wazero v1.8.0
golang.org/x/crypto v0.26.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.24.0
golang.org/x/text v0.17.0
golang.org/x/sys v0.25.0
golang.org/x/text v0.18.0
lukechampine.com/adiantum v1.1.1
)

8
go.sum
View File

@@ -14,9 +14,9 @@ golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=

View File

@@ -5,7 +5,7 @@ go 1.21
toolchain go1.23.0
require (
github.com/ncruces/go-sqlite3 v0.17.1
github.com/ncruces/go-sqlite3 v0.18.1
gorm.io/gorm v1.25.11
)
@@ -14,6 +14,6 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
)

View File

@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/ncruces/go-sqlite3 v0.17.1 h1:VxTjDpCn87FaFlKMaAYC1jP7ND0d4UNj+6G4IQDHbgI=
github.com/ncruces/go-sqlite3 v0.17.1/go.mod h1:FnCyui8SlDoL0mQZ5dTouNo7s7jXS0kJv9lBt1GlM9w=
github.com/ncruces/go-sqlite3 v0.18.1 h1:iN8IMZV5EMxpH88NUac9vId23eTKNFUhP7jgY0EBbNc=
github.com/ncruces/go-sqlite3 v0.18.1/go.mod h1:eEOyZnW1dGTJ+zDpMuzfYamEUBtdFz5zeYhqLBtHxvM=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

View File

@@ -24,6 +24,7 @@
#define SQLITE_ENABLE_ATOMIC_WRITE
#define SQLITE_ENABLE_BATCH_ATOMIC_WRITE
#define SQLITE_ENABLE_COLUMN_METADATA
#define SQLITE_ENABLE_SETLK_TIMEOUT 2
#define SQLITE_ENABLE_STAT4 1
// We have our own memdb VFS.

View File

@@ -1,7 +1,9 @@
package tests
import (
"errors"
"io"
"log"
"net/url"
"os"
"os/exec"
@@ -19,6 +21,20 @@ import (
"github.com/ncruces/go-sqlite3/vfs/memdb"
)
func TestMain(m *testing.M) {
sqlite3.AutoExtension(func(c *sqlite3.Conn) error {
return c.ConfigLog(func(code sqlite3.ExtendedErrorCode, msg string) {
// Having to do journal recovery is unexpected.
if errors.Is(code, sqlite3.NOTICE) {
log.Panicf("%v (%d): %s", code, code, msg)
} else {
log.Printf("%v (%d): %s", code, code, msg)
}
})
})
m.Run()
}
func Test_parallel(t *testing.T) {
if !vfs.SupportsFileLocking {
t.Skip("skipping without locks")
@@ -45,12 +61,19 @@ func Test_wal(t *testing.T) {
t.Skip("skipping without shared memory")
}
var iter int
if testing.Short() {
iter = 1000
} else {
iter = 2500
}
name := "file:" +
filepath.ToSlash(filepath.Join(t.TempDir(), "test.db")) +
"?_pragma=busy_timeout(10000)" +
"&_pragma=journal_mode(wal)" +
"&_pragma=synchronous(off)"
testParallel(t, name, 1000)
testParallel(t, name, iter)
testIntegrity(t, name)
}
@@ -108,7 +131,12 @@ func TestMultiProcess(t *testing.T) {
"&_pragma=journal_mode(truncate)" +
"&_pragma=synchronous(off)"
cmd := exec.Command(os.Args[0], append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...)
exe, err := os.Executable()
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(exe, append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...)
out, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)

View File

@@ -79,16 +79,15 @@ func (f *vfsFile) Lock(lock LockLevel) error {
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if f.lock < LOCK_PENDING {
// If we're already RESERVED, we can block indefinitely,
// since only new readers may briefly hold the PENDING lock.
// since only incoming readers may briefly hold the PENDING lock.
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
return rc
}
f.lock = LOCK_PENDING
}
// We already have PENDING, so we're just waiting for readers to leave.
// If we were RESERVED, we can wait for a little while, before invoking
// the busy handler; we will only do this once.
if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK {
// We are now PENDING, so we're just waiting for readers to leave.
// If we were RESERVED, we can block for a bit before invoking the busy handler.
if rc := osGetExclusiveLock(f.File, reserved /* block */); rc != _OK {
return rc
}
f.lock = LOCK_EXCLUSIVE

View File

@@ -53,10 +53,11 @@ func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, d
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Since(before) {
if time.Since(before) > timeout {
break
}
time.Sleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms
time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
}
}
return osLockErrorCode(err, def)

View File

@@ -32,9 +32,9 @@ func osGetPendingLock(file *os.File, block bool) _ErrorCode {
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
}
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode {
var timeout time.Duration
if wait {
if block {
timeout = time.Millisecond
}
// Acquire the EXCLUSIVE lock.

View File

@@ -38,9 +38,9 @@ func osGetPendingLock(file *os.File, block bool) _ErrorCode {
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
}
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
func osGetExclusiveLock(file *os.File, block bool) _ErrorCode {
var timeout time.Duration
if wait {
if block {
timeout = time.Millisecond
}
@@ -134,10 +134,11 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
break
}
if timeout < time.Since(before) {
if time.Since(before) > timeout {
break
}
time.Sleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms
time.Sleep(time.Duration(rand.Int63() & sleepIncrement))
}
}
return osLockErrorCode(err, def)

View File

@@ -6,6 +6,7 @@ import (
"context"
"io"
"os"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/tetratelabs/wazero/api"
@@ -49,6 +50,7 @@ type vfsShm struct {
path string
regions []*util.MappedRegion
readOnly bool
blocking bool
}
func (s *vfsShm) shmOpen() _ErrorCode {
@@ -76,6 +78,13 @@ func (s *vfsShm) shmOpen() _ErrorCode {
if s.readOnly {
return _READONLY_CANTINIT
}
// Do not use a blocking lock here.
// If the lock cannot be obtained immediately,
// it means some other connection is truncating the file.
// And after it has done so, it will not release its lock,
// but only downgrade it to a shared lock.
// So no point in blocking here.
// The call below to obtain the shared DMS lock may use a blocking lock.
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
return rc
}
@@ -83,7 +92,7 @@ func (s *vfsShm) shmOpen() _ErrorCode {
return _IOERR_SHMOPEN
}
}
if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
if rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond); rc != _OK {
return rc
}
return _OK
@@ -150,13 +159,18 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
panic(util.AssertErr())
}
var timeout time.Duration
if s.blocking {
timeout = time.Millisecond
}
switch {
case flags&_SHM_UNLOCK != 0:
return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n))
case flags&_SHM_SHARED != 0:
return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout)
case flags&_SHM_EXCLUSIVE != 0:
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout)
default:
panic(util.AssertErr())
}
@@ -181,3 +195,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
s.Close()
s.File = nil
}
func (s *vfsShm) shmEnableBlocking(block bool) {
s.blocking = block
}

View File

@@ -121,8 +121,8 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
// Find a shared file, increase the reference count.
for _, g := range vfsShmFiles {
if g != nil && os.SameFile(fi, g.info) {
g.refs++
s.vfsShmFile = g
g.refs++
return _OK
}
}
@@ -207,15 +207,22 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
case flags&_SHM_UNLOCK != 0:
for i := offset; i < offset+n; i++ {
if s.lock[i] {
if s.vfsShmFile.lock[i] == 0 {
panic(util.AssertErr())
}
if s.vfsShmFile.lock[i] <= 0 {
s.vfsShmFile.lock[i] = 0
} else {
s.vfsShmFile.lock[i]--
}
s.lock[i] = false
}
}
case flags&_SHM_SHARED != 0:
for i := offset; i < offset+n; i++ {
if s.lock[i] {
panic(util.AssertErr())
}
if s.vfsShmFile.lock[i] < 0 {
return _BUSY
}
@@ -226,6 +233,9 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
}
case flags&_SHM_EXCLUSIVE != 0:
for i := offset; i < offset+n; i++ {
if s.lock[i] {
panic(util.AssertErr())
}
if s.vfsShmFile.lock[i] != 0 {
return _BUSY
}

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4d58c92d45fb60dc2eea461b0e7c1d512cb1dd00deafdfaab3e24b943f714f69
size 475960
oid sha256:42858f7755a42e64913c5119dcd5ecb3fbceac71e13952d4e8b62a97c05311b5
size 476253

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:90eb053e2a17bd73d8fb17f82c60e595b71018a7880c7b04d7f4b6aae187f5a5
size 489132
oid sha256:cf4fb9d930647e29f0da71981b6b1e204e4949c816e5f7d6aeded6a90ad9e8ab
size 489516

View File

@@ -243,6 +243,15 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
return _OK
}
case _FCNTL_LOCK_TIMEOUT:
if file, ok := file.(FileSharedMemory); ok {
if iface, ok := file.SharedMemory().(interface{ shmEnableBlocking(bool) }); ok {
if i := util.ReadUint32(mod, pArg); i == 0 || i == 1 {
iface.shmEnableBlocking(i != 0)
}
}
}
case _FCNTL_PERSIST_WAL:
if file, ok := file.(FilePersistentWAL); ok {
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {