bump ncruces/go-sqlite3 to v0.20.2 (#3524)
This commit is contained in:
parent
b84637801a
commit
45e1609377
2
go.mod
2
go.mod
|
@ -43,7 +43,7 @@ require (
|
||||||
github.com/miekg/dns v1.1.62
|
github.com/miekg/dns v1.1.62
|
||||||
github.com/minio/minio-go/v7 v7.0.80
|
github.com/minio/minio-go/v7 v7.0.80
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/ncruces/go-sqlite3 v0.20.0
|
github.com/ncruces/go-sqlite3 v0.20.2
|
||||||
github.com/oklog/ulid v1.3.1
|
github.com/oklog/ulid v1.3.1
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
|
|
|
@ -432,8 +432,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/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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncruces/go-sqlite3 v0.20.0 h1:/nBLvYxj7sk9S6y57nmMFvoQ/KJtGo0pNi8J80s8oJU=
|
github.com/ncruces/go-sqlite3 v0.20.2 h1:cMLIwrLZQuCWVCEOowSqlIlpzgbag3jnYVW4NM5u01M=
|
||||||
github.com/ncruces/go-sqlite3 v0.20.0/go.mod h1:yL4ZNWGsr1/8pcLfpPW1RT1WFdvyeHonrgIwwi4rvkg=
|
github.com/ncruces/go-sqlite3 v0.20.2/go.mod h1:yL4ZNWGsr1/8pcLfpPW1RT1WFdvyeHonrgIwwi4rvkg=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
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/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||||
|
|
|
@ -2,6 +2,7 @@ package sqlite3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
@ -70,6 +71,15 @@ func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log writes a message into the error log established by [Conn.ConfigLog].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/log.html
|
||||||
|
func (c *Conn) Log(code ExtendedErrorCode, format string, a ...any) {
|
||||||
|
if c.log != nil {
|
||||||
|
c.log(code, fmt.Sprintf(format, a...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FileControl allows low-level control of database files.
|
// FileControl allows low-level control of database files.
|
||||||
// Only a subset of opcodes are supported.
|
// Only a subset of opcodes are supported.
|
||||||
//
|
//
|
||||||
|
|
|
@ -89,6 +89,7 @@ func (ctx Context) ResultText(value string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResultRawText sets the text result of the function to a []byte.
|
// ResultRawText sets the text result of the function to a []byte.
|
||||||
|
// Returning a nil slice is the same as calling [Context.ResultNull].
|
||||||
//
|
//
|
||||||
// https://sqlite.org/c3ref/result_blob.html
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
func (ctx Context) ResultRawText(value []byte) {
|
func (ctx Context) ResultRawText(value []byte) {
|
||||||
|
|
|
@ -106,6 +106,11 @@ func (e ErrorCode) Temporary() bool {
|
||||||
return e == BUSY
|
return e == BUSY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtendedCode returns the extended error code for this error.
|
||||||
|
func (e ErrorCode) ExtendedCode() ExtendedErrorCode {
|
||||||
|
return ExtendedErrorCode(e)
|
||||||
|
}
|
||||||
|
|
||||||
// Error implements the error interface.
|
// Error implements the error interface.
|
||||||
func (e ExtendedErrorCode) Error() string {
|
func (e ExtendedErrorCode) Error() string {
|
||||||
return util.ErrorCodeString(uint32(e))
|
return util.ErrorCodeString(uint32(e))
|
||||||
|
@ -136,6 +141,11 @@ func (e ExtendedErrorCode) Timeout() bool {
|
||||||
return e == BUSY_TIMEOUT
|
return e == BUSY_TIMEOUT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code returns the primary error code for this error.
|
||||||
|
func (e ExtendedErrorCode) Code() ErrorCode {
|
||||||
|
return ErrorCode(e)
|
||||||
|
}
|
||||||
|
|
||||||
func errorCode(err error, def ErrorCode) (msg string, code uint32) {
|
func errorCode(err error, def ErrorCode) (msg string, code uint32) {
|
||||||
switch code := err.(type) {
|
switch code := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build unix && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_noshm || sqlite3_nosys)
|
//go:build unix && !sqlite3_nosys
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
@ -55,10 +55,10 @@ type MappedRegion struct {
|
||||||
used bool
|
used bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, prot int) (*MappedRegion, error) {
|
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, readOnly bool) (*MappedRegion, error) {
|
||||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||||
r := s.new(ctx, mod, size)
|
r := s.new(ctx, mod, size)
|
||||||
err := r.mmap(f, offset, prot)
|
err := r.mmap(f, offset, readOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,11 @@ func (r *MappedRegion) Unmap() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *MappedRegion) mmap(f *os.File, offset int64, prot int) error {
|
func (r *MappedRegion) mmap(f *os.File, offset int64, readOnly bool) error {
|
||||||
|
prot := unix.PROT_READ
|
||||||
|
if !readOnly {
|
||||||
|
prot |= unix.PROT_WRITE
|
||||||
|
}
|
||||||
_, err := unix.MmapPtr(int(f.Fd()), offset, r.addr, uintptr(r.size),
|
_, err := unix.MmapPtr(int(f.Fd()), offset, r.addr, uintptr(r.size),
|
||||||
prot, unix.MAP_SHARED|unix.MAP_FIXED)
|
prot, unix.MAP_SHARED|unix.MAP_FIXED)
|
||||||
r.used = err == nil
|
r.used = err == nil
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !unix || !(386 || arm || amd64 || arm64 || riscv64 || ppc64le) || sqlite3_noshm || sqlite3_nosys
|
//go:build !unix || sqlite3_nosys
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
|
53
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_windows.go
generated
vendored
Normal file
53
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
//go:build !sqlite3_nosys
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MappedRegion struct {
|
||||||
|
windows.Handle
|
||||||
|
Data []byte
|
||||||
|
addr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32) (*MappedRegion, error) {
|
||||||
|
h, err := windows.CreateFileMapping(windows.Handle(f.Fd()), nil, windows.PAGE_READWRITE, 0, 0, nil)
|
||||||
|
if h == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := windows.MapViewOfFile(h, windows.FILE_MAP_WRITE,
|
||||||
|
uint32(offset>>32), uint32(offset), uintptr(size))
|
||||||
|
if a == 0 {
|
||||||
|
windows.CloseHandle(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &MappedRegion{Handle: h, addr: a}
|
||||||
|
// SliceHeader, although deprecated, avoids a go vet warning.
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&res.Data))
|
||||||
|
sh.Len = int(size)
|
||||||
|
sh.Cap = int(size)
|
||||||
|
sh.Data = a
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MappedRegion) Unmap() error {
|
||||||
|
if r.Data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := windows.UnmapViewOfFile(r.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Data = nil
|
||||||
|
return windows.CloseHandle(r.Handle)
|
||||||
|
}
|
|
@ -255,6 +255,7 @@ func (s *Stmt) BindText(param int, value string) error {
|
||||||
|
|
||||||
// BindRawText binds a []byte to the prepared statement as text.
|
// BindRawText binds a []byte to the prepared statement as text.
|
||||||
// The leftmost SQL parameter has an index of 1.
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
// Binding a nil slice is the same as calling [Stmt.BindNull].
|
||||||
//
|
//
|
||||||
// https://sqlite.org/c3ref/bind_blob.html
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
func (s *Stmt) BindRawText(param int, value []byte) error {
|
func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||||
|
|
|
@ -15,24 +15,23 @@ The main differences are [file locking](#file-locking) and [WAL mode](#write-ahe
|
||||||
|
|
||||||
POSIX advisory locks, which SQLite uses on Unix, are
|
POSIX advisory locks, which SQLite uses on Unix, are
|
||||||
[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161).
|
[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161).
|
||||||
|
Instead, on Linux and macOS, this package uses
|
||||||
On Linux and macOS, this package uses
|
|
||||||
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||||
to synchronize access to database files.
|
to synchronize access to database files.
|
||||||
OFD locks are fully compatible with POSIX advisory locks.
|
|
||||||
|
|
||||||
This package can also use
|
This package can also use
|
||||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2),
|
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2),
|
||||||
albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`).
|
albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`).
|
||||||
On BSD, macOS, and illumos, BSD locks are fully compatible with POSIX advisory locks;
|
|
||||||
on Linux and z/OS, they are fully functional, but incompatible;
|
|
||||||
elsewhere, they are very likely broken.
|
|
||||||
BSD locks are the default on BSD and illumos,
|
BSD locks are the default on BSD and illumos,
|
||||||
but you can opt into them with the `sqlite3_flock` build tag.
|
but you can opt into them with the `sqlite3_flock` build tag.
|
||||||
|
|
||||||
On Windows, this package uses `LockFileEx` and `UnlockFileEx`,
|
On Windows, this package uses `LockFileEx` and `UnlockFileEx`,
|
||||||
like SQLite.
|
like SQLite.
|
||||||
|
|
||||||
|
You can also opt into a cross-platform locking implementation
|
||||||
|
with the `sqlite3_dotlk` build tag.
|
||||||
|
The only requirement is an atomic `os.Mkdir`.
|
||||||
|
|
||||||
Otherwise, file locking is not supported, and you must use
|
Otherwise, file locking is not supported, and you must use
|
||||||
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
|
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
|
||||||
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
|
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
|
||||||
|
@ -46,7 +45,7 @@ to check if your build supports file locking.
|
||||||
|
|
||||||
### Write-Ahead Logging
|
### Write-Ahead Logging
|
||||||
|
|
||||||
On little-endian Unix, this package uses `mmap` to implement
|
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),
|
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
|
||||||
like SQLite.
|
like SQLite.
|
||||||
|
|
||||||
|
@ -55,6 +54,11 @@ a WAL database can only be accessed by a single proccess.
|
||||||
Other processes that attempt to access a database locked with BSD locks,
|
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.
|
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
|
||||||
|
with the `sqlite3_dotlk` build tag.
|
||||||
|
|
||||||
Otherwise, [WAL support is limited](https://sqlite.org/wal.html#noshm),
|
Otherwise, [WAL support is limited](https://sqlite.org/wal.html#noshm),
|
||||||
and `EXCLUSIVE` locking mode must be set to create, read, and write WAL databases.
|
and `EXCLUSIVE` locking mode must be set to create, read, and write WAL databases.
|
||||||
To use `EXCLUSIVE` locking mode with the
|
To use `EXCLUSIVE` locking mode with the
|
||||||
|
@ -67,7 +71,7 @@ to check if your build supports shared memory.
|
||||||
|
|
||||||
### Batch-Atomic Write
|
### Batch-Atomic Write
|
||||||
|
|
||||||
On 64-bit Linux, this package supports
|
On Linux, this package may support
|
||||||
[batch-atomic writes](https://sqlite.org/cgi/src/technote/714)
|
[batch-atomic writes](https://sqlite.org/cgi/src/technote/714)
|
||||||
on the F2FS filesystem.
|
on the F2FS filesystem.
|
||||||
|
|
||||||
|
@ -86,27 +90,27 @@ The implementation is compatible with SQLite's
|
||||||
### Build Tags
|
### Build Tags
|
||||||
|
|
||||||
The VFS can be customized with a few build tags:
|
The VFS can be customized with a few build tags:
|
||||||
- `sqlite3_flock` forces the use of BSD locks; it can be used on z/OS to enable locking,
|
- `sqlite3_flock` forces the use of BSD locks.
|
||||||
and elsewhere to test BSD locks.
|
- `sqlite3_dotlk` forces the use of dot-file locks.
|
||||||
- `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys);
|
- `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys).
|
||||||
disables locking _and_ shared memory on all platforms.
|
|
||||||
- `sqlite3_noshm` disables shared memory on all platforms.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The default configuration of this package is compatible with the standard
|
> The default configuration of this package is compatible with the standard
|
||||||
> [Unix and Windows SQLite VFSes](https://sqlite.org/vfs.html#multiple_vfses);
|
> [Unix and Windows SQLite VFSes](https://sqlite.org/vfs.html#multiple_vfses);
|
||||||
> `sqlite3_flock` builds are compatible with the
|
> `sqlite3_flock` builds are compatible with the
|
||||||
> [`unix-flock` VFS](https://sqlite.org/compile.html#enable_locking_style).
|
> [`unix-flock` VFS](https://sqlite.org/compile.html#enable_locking_style);
|
||||||
|
> `sqlite3_dotlk` builds are compatible with the
|
||||||
|
> [`unix-dotfile` VFS](https://sqlite.org/compile.html#enable_locking_style).
|
||||||
> If incompatible file locking is used, accessing databases concurrently with
|
> If incompatible file locking is used, accessing databases concurrently with
|
||||||
> _other_ SQLite libraries will eventually corrupt data.
|
> _other_ SQLite libraries will eventually corrupt data.
|
||||||
|
|
||||||
### Custom VFSes
|
### Custom VFSes
|
||||||
|
|
||||||
- [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum)
|
|
||||||
wraps a VFS to offer encryption at rest.
|
|
||||||
- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb)
|
- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb)
|
||||||
implements an in-memory VFS.
|
implements an in-memory VFS.
|
||||||
- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs)
|
- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs)
|
||||||
implements a VFS for immutable databases.
|
implements a VFS for immutable databases.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum)
|
||||||
|
wraps a VFS to offer encryption at rest.
|
||||||
- [`github.com/ncruces/go-sqlite3/vfs/xts`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/xts)
|
- [`github.com/ncruces/go-sqlite3/vfs/xts`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/xts)
|
||||||
wraps a VFS to offer encryption at rest.
|
wraps a VFS to offer encryption at rest.
|
|
@ -234,4 +234,8 @@ const (
|
||||||
_SHM_LOCK _ShmFlag = 2
|
_SHM_LOCK _ShmFlag = 2
|
||||||
_SHM_SHARED _ShmFlag = 4
|
_SHM_SHARED _ShmFlag = 4
|
||||||
_SHM_EXCLUSIVE _ShmFlag = 8
|
_SHM_EXCLUSIVE _ShmFlag = 8
|
||||||
|
|
||||||
|
_SHM_NLOCK = 8
|
||||||
|
_SHM_BASE = 120
|
||||||
|
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,10 +35,10 @@ func testSymlinks(path string) error {
|
||||||
|
|
||||||
func (vfsOS) Delete(path string, syncDir bool) error {
|
func (vfsOS) Delete(path string, syncDir bool) error {
|
||||||
err := os.Remove(path)
|
err := os.Remove(path)
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return _IOERR_DELETE_NOENT
|
return _IOERR_DELETE_NOENT
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if runtime.GOOS != "windows" && syncDir {
|
if runtime.GOOS != "windows" && syncDir {
|
||||||
|
@ -151,6 +151,7 @@ func (f *vfsFile) Close() error {
|
||||||
if f.shm != nil {
|
if f.shm != nil {
|
||||||
f.shm.Close()
|
f.shm.Close()
|
||||||
}
|
}
|
||||||
|
f.Unlock(LOCK_NONE)
|
||||||
return f.File.Close()
|
return f.File.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,10 +207,10 @@ func (f *vfsFile) HasMoved() (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
pi, err := os.Stat(f.Name())
|
pi, err := os.Stat(f.Name())
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return !os.SameFile(fi, pi), nil
|
return !os.SameFile(fi, pi), nil
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
|
//go:build ((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || sqlite3_nosys
|
//go:build !(((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk)
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
|
|
@ -78,19 +78,15 @@ type memDB struct {
|
||||||
|
|
||||||
// +checklocks:dataMtx
|
// +checklocks:dataMtx
|
||||||
data []*[sectorSize]byte
|
data []*[sectorSize]byte
|
||||||
|
|
||||||
// +checklocks:dataMtx
|
// +checklocks:dataMtx
|
||||||
size int64
|
size int64
|
||||||
|
|
||||||
// +checklocks:lockMtx
|
|
||||||
shared int32
|
|
||||||
// +checklocks:lockMtx
|
|
||||||
reserved bool
|
|
||||||
// +checklocks:lockMtx
|
|
||||||
pending bool
|
|
||||||
|
|
||||||
// +checklocks:memoryMtx
|
// +checklocks:memoryMtx
|
||||||
refs int
|
refs int32
|
||||||
|
|
||||||
|
shared int32 // +checklocks:lockMtx
|
||||||
|
pending bool // +checklocks:lockMtx
|
||||||
|
reserved bool // +checklocks:lockMtx
|
||||||
|
|
||||||
lockMtx sync.Mutex
|
lockMtx sync.Mutex
|
||||||
dataMtx sync.RWMutex
|
dataMtx sync.RWMutex
|
||||||
|
@ -253,12 +249,12 @@ func (m *memFile) Unlock(lock vfs.LockLevel) error {
|
||||||
m.lockMtx.Lock()
|
m.lockMtx.Lock()
|
||||||
defer m.lockMtx.Unlock()
|
defer m.lockMtx.Unlock()
|
||||||
|
|
||||||
if m.pending && m.lock >= vfs.LOCK_PENDING {
|
if m.lock >= vfs.LOCK_RESERVED {
|
||||||
m.pending = false
|
|
||||||
}
|
|
||||||
if m.reserved && m.lock >= vfs.LOCK_RESERVED {
|
|
||||||
m.reserved = false
|
m.reserved = false
|
||||||
}
|
}
|
||||||
|
if m.lock >= vfs.LOCK_PENDING {
|
||||||
|
m.pending = false
|
||||||
|
}
|
||||||
if lock < vfs.LOCK_SHARED {
|
if lock < vfs.LOCK_SHARED {
|
||||||
m.shared--
|
m.shared--
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
|
//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && !(sqlite3_dotlk || sqlite3_nosys)) || sqlite3_flock
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
//go:build sqlite3_dotlk
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// +checklocks:vfsDotLocksMtx
|
||||||
|
vfsDotLocks = map[string]*vfsDotLocker{}
|
||||||
|
vfsDotLocksMtx sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type vfsDotLocker struct {
|
||||||
|
shared int // +checklocks:vfsDotLocksMtx
|
||||||
|
pending *os.File // +checklocks:vfsDotLocksMtx
|
||||||
|
reserved *os.File // +checklocks:vfsDotLocksMtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||||
|
vfsDotLocksMtx.Lock()
|
||||||
|
defer vfsDotLocksMtx.Unlock()
|
||||||
|
|
||||||
|
name := file.Name()
|
||||||
|
locker := vfsDotLocks[name]
|
||||||
|
if locker == nil {
|
||||||
|
err := os.Mkdir(name+".lock", 0777)
|
||||||
|
if errors.Is(err, fs.ErrExist) {
|
||||||
|
return _BUSY // Another process has the lock.
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
}
|
||||||
|
locker = &vfsDotLocker{}
|
||||||
|
vfsDotLocks[name] = locker
|
||||||
|
}
|
||||||
|
|
||||||
|
if locker.pending != nil {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
locker.shared++
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||||
|
vfsDotLocksMtx.Lock()
|
||||||
|
defer vfsDotLocksMtx.Unlock()
|
||||||
|
|
||||||
|
name := file.Name()
|
||||||
|
locker := vfsDotLocks[name]
|
||||||
|
if locker == nil {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
if locker.reserved != nil && locker.reserved != file {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
locker.reserved = file
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetExclusiveLock(file *os.File, _ *LockLevel) _ErrorCode {
|
||||||
|
vfsDotLocksMtx.Lock()
|
||||||
|
defer vfsDotLocksMtx.Unlock()
|
||||||
|
|
||||||
|
name := file.Name()
|
||||||
|
locker := vfsDotLocks[name]
|
||||||
|
if locker == nil {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
if locker.pending != nil && locker.pending != file {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
locker.pending = file
|
||||||
|
if locker.shared > 1 {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osDowngradeLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||||
|
vfsDotLocksMtx.Lock()
|
||||||
|
defer vfsDotLocksMtx.Unlock()
|
||||||
|
|
||||||
|
name := file.Name()
|
||||||
|
locker := vfsDotLocks[name]
|
||||||
|
if locker == nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
if locker.reserved == file {
|
||||||
|
locker.reserved = nil
|
||||||
|
}
|
||||||
|
if locker.pending == file {
|
||||||
|
locker.pending = nil
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||||
|
vfsDotLocksMtx.Lock()
|
||||||
|
defer vfsDotLocksMtx.Unlock()
|
||||||
|
|
||||||
|
name := file.Name()
|
||||||
|
locker := vfsDotLocks[name]
|
||||||
|
if locker == nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
if locker.shared == 1 {
|
||||||
|
err := os.Remove(name + ".lock")
|
||||||
|
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
delete(vfsDotLocks, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if locker.reserved == file {
|
||||||
|
locker.reserved = nil
|
||||||
|
}
|
||||||
|
if locker.pending == file {
|
||||||
|
locker.pending = nil
|
||||||
|
}
|
||||||
|
locker.shared--
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||||
|
vfsDotLocksMtx.Lock()
|
||||||
|
defer vfsDotLocksMtx.Unlock()
|
||||||
|
|
||||||
|
name := file.Name()
|
||||||
|
locker := vfsDotLocks[name]
|
||||||
|
if locker == nil {
|
||||||
|
return false, _OK
|
||||||
|
}
|
||||||
|
return locker.reserved != nil, _OK
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build (linux || darwin) && !(sqlite3_flock || sqlite3_nosys)
|
//go:build (linux || darwin) && !(sqlite3_flock || sqlite3_dotlk || sqlite3_nosys)
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !sqlite3_nosys
|
//go:build !(sqlite3_dotlk || sqlite3_nosys)
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
//go:build (darwin || linux) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys)
|
//go:build ((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/ncruces/go-sqlite3/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SupportsSharedMemory is false on platforms that do not support shared memory.
|
// SupportsSharedMemory is false on platforms that do not support shared memory.
|
||||||
// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode].
|
// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode].
|
||||||
//
|
//
|
||||||
|
@ -22,12 +9,6 @@ import (
|
||||||
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
|
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
|
||||||
const SupportsSharedMemory = true
|
const SupportsSharedMemory = true
|
||||||
|
|
||||||
const (
|
|
||||||
_SHM_NLOCK = 8
|
|
||||||
_SHM_BASE = 120
|
|
||||||
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *vfsFile) SharedMemory() SharedMemory { return f.shm }
|
func (f *vfsFile) SharedMemory() SharedMemory { return f.shm }
|
||||||
|
|
||||||
// NewSharedMemory returns a shared-memory WAL-index
|
// NewSharedMemory returns a shared-memory WAL-index
|
||||||
|
@ -41,172 +22,5 @@ func NewSharedMemory(path string, flags OpenFlag) SharedMemory {
|
||||||
if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 {
|
if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &vfsShm{
|
return &vfsShm{path: path}
|
||||||
path: path,
|
|
||||||
readOnly: flags&OPEN_READONLY != 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ blockingSharedMemory = &vfsShm{}
|
|
||||||
|
|
||||||
type vfsShm struct {
|
|
||||||
*os.File
|
|
||||||
path string
|
|
||||||
regions []*util.MappedRegion
|
|
||||||
readOnly bool
|
|
||||||
blocking bool
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vfsShm) shmOpen() _ErrorCode {
|
|
||||||
if s.File == nil {
|
|
||||||
var flag int
|
|
||||||
if s.readOnly {
|
|
||||||
flag = unix.O_RDONLY
|
|
||||||
} else {
|
|
||||||
flag = unix.O_RDWR
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(s.path,
|
|
||||||
flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return _CANTOPEN
|
|
||||||
}
|
|
||||||
s.File = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dead man's switch.
|
|
||||||
if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK {
|
|
||||||
return _IOERR_LOCK
|
|
||||||
} else if lock == unix.F_WRLCK {
|
|
||||||
return _BUSY
|
|
||||||
} else if lock == unix.F_UNLCK {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if err := s.Truncate(0); err != nil {
|
|
||||||
return _IOERR_SHMOPEN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond); rc != _OK {
|
|
||||||
return rc
|
|
||||||
}
|
|
||||||
return _OK
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
|
|
||||||
// Ensure size is a multiple of the OS page size.
|
|
||||||
if int(size)&(unix.Getpagesize()-1) != 0 {
|
|
||||||
return 0, _IOERR_SHMMAP
|
|
||||||
}
|
|
||||||
|
|
||||||
if rc := s.shmOpen(); rc != _OK {
|
|
||||||
return 0, rc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if file is big enough.
|
|
||||||
o, err := s.Seek(0, io.SeekEnd)
|
|
||||||
if err != nil {
|
|
||||||
return 0, _IOERR_SHMSIZE
|
|
||||||
}
|
|
||||||
if n := (int64(id) + 1) * int64(size); n > o {
|
|
||||||
if !extend {
|
|
||||||
return 0, _OK
|
|
||||||
}
|
|
||||||
err := osAllocate(s.File, n)
|
|
||||||
if err != nil {
|
|
||||||
return 0, _IOERR_SHMSIZE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var prot int
|
|
||||||
if s.readOnly {
|
|
||||||
prot = unix.PROT_READ
|
|
||||||
} else {
|
|
||||||
prot = unix.PROT_READ | unix.PROT_WRITE
|
|
||||||
}
|
|
||||||
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot)
|
|
||||||
if err != nil {
|
|
||||||
return 0, _IOERR_SHMMAP
|
|
||||||
}
|
|
||||||
s.regions = append(s.regions, r)
|
|
||||||
if s.readOnly {
|
|
||||||
return r.Ptr, _READONLY
|
|
||||||
}
|
|
||||||
return r.Ptr, _OK
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
|
||||||
// Argument check.
|
|
||||||
if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK {
|
|
||||||
panic(util.AssertErr())
|
|
||||||
}
|
|
||||||
switch flags {
|
|
||||||
case
|
|
||||||
_SHM_LOCK | _SHM_SHARED,
|
|
||||||
_SHM_LOCK | _SHM_EXCLUSIVE,
|
|
||||||
_SHM_UNLOCK | _SHM_SHARED,
|
|
||||||
_SHM_UNLOCK | _SHM_EXCLUSIVE:
|
|
||||||
//
|
|
||||||
default:
|
|
||||||
panic(util.AssertErr())
|
|
||||||
}
|
|
||||||
if n != 1 && flags&_SHM_EXCLUSIVE == 0 {
|
|
||||||
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), timeout)
|
|
||||||
case flags&_SHM_EXCLUSIVE != 0:
|
|
||||||
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout)
|
|
||||||
default:
|
|
||||||
panic(util.AssertErr())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vfsShm) shmUnmap(delete bool) {
|
|
||||||
if s.File == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmap regions.
|
|
||||||
for _, r := range s.regions {
|
|
||||||
r.Unmap()
|
|
||||||
}
|
|
||||||
clear(s.regions)
|
|
||||||
s.regions = s.regions[:0]
|
|
||||||
|
|
||||||
// Close the file.
|
|
||||||
if delete {
|
|
||||||
os.Remove(s.path)
|
|
||||||
}
|
|
||||||
s.Close()
|
|
||||||
s.File = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vfsShm) shmBarrier() {
|
|
||||||
s.Lock()
|
|
||||||
//lint:ignore SA2001 memory barrier.
|
|
||||||
s.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *vfsShm) shmEnableBlocking(block bool) {
|
|
||||||
s.blocking = block
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_noshm || sqlite3_nosys)
|
//go:build ((freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_dotlk || sqlite3_nosys)) || sqlite3_flock
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
@ -14,44 +14,14 @@ import (
|
||||||
"github.com/ncruces/go-sqlite3/internal/util"
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SupportsSharedMemory is false on platforms that do not support shared memory.
|
|
||||||
// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode].
|
|
||||||
//
|
|
||||||
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
|
|
||||||
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
|
|
||||||
const SupportsSharedMemory = true
|
|
||||||
|
|
||||||
const _SHM_NLOCK = 8
|
|
||||||
|
|
||||||
func (f *vfsFile) SharedMemory() SharedMemory { return f.shm }
|
|
||||||
|
|
||||||
// NewSharedMemory returns a shared-memory WAL-index
|
|
||||||
// backed by a file with the given path.
|
|
||||||
// It will return nil if shared-memory is not supported,
|
|
||||||
// or not appropriate for the given flags.
|
|
||||||
// Only [OPEN_MAIN_DB] databases may need a WAL-index.
|
|
||||||
// You must ensure all concurrent accesses to a database
|
|
||||||
// use shared-memory instances created with the same path.
|
|
||||||
func NewSharedMemory(path string, flags OpenFlag) SharedMemory {
|
|
||||||
if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &vfsShm{
|
|
||||||
path: path,
|
|
||||||
readOnly: flags&OPEN_READONLY != 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type vfsShmFile struct {
|
type vfsShmFile struct {
|
||||||
*os.File
|
*os.File
|
||||||
info os.FileInfo
|
info os.FileInfo
|
||||||
|
|
||||||
// +checklocks:vfsShmFilesMtx
|
refs int // +checklocks:vfsShmFilesMtx
|
||||||
refs int
|
|
||||||
|
|
||||||
// +checklocks:lockMtx
|
lock [_SHM_NLOCK]int16 // +checklocks:Mutex
|
||||||
lock [_SHM_NLOCK]int16
|
sync.Mutex
|
||||||
lockMtx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -65,7 +35,6 @@ type vfsShm struct {
|
||||||
path string
|
path string
|
||||||
lock [_SHM_NLOCK]bool
|
lock [_SHM_NLOCK]bool
|
||||||
regions []*util.MappedRegion
|
regions []*util.MappedRegion
|
||||||
readOnly bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *vfsShm) Close() error {
|
func (s *vfsShm) Close() error {
|
||||||
|
@ -80,7 +49,7 @@ func (s *vfsShm) Close() error {
|
||||||
s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
|
s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
|
||||||
|
|
||||||
// Decrease reference count.
|
// Decrease reference count.
|
||||||
if s.vfsShmFile.refs > 1 {
|
if s.vfsShmFile.refs > 0 {
|
||||||
s.vfsShmFile.refs--
|
s.vfsShmFile.refs--
|
||||||
s.vfsShmFile = nil
|
s.vfsShmFile = nil
|
||||||
return nil
|
return nil
|
||||||
|
@ -97,7 +66,7 @@ func (s *vfsShm) Close() error {
|
||||||
panic(util.AssertErr())
|
panic(util.AssertErr())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||||
if s.vfsShmFile != nil {
|
if s.vfsShmFile != nil {
|
||||||
return _OK
|
return _OK
|
||||||
}
|
}
|
||||||
|
@ -128,34 +97,29 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock and truncate the file, if not readonly.
|
// Lock and truncate the file.
|
||||||
// The lock is only released by closing the file.
|
// The lock is only released by closing the file.
|
||||||
if s.readOnly {
|
|
||||||
rc = _READONLY_CANTINIT
|
|
||||||
} else {
|
|
||||||
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK {
|
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK {
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
if err := f.Truncate(0); err != nil {
|
if err := f.Truncate(0); err != nil {
|
||||||
return _IOERR_SHMOPEN
|
return _IOERR_SHMOPEN
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add the new shared file.
|
// Add the new shared file.
|
||||||
s.vfsShmFile = &vfsShmFile{
|
s.vfsShmFile = &vfsShmFile{
|
||||||
File: f,
|
File: f,
|
||||||
info: fi,
|
info: fi,
|
||||||
refs: 1,
|
|
||||||
}
|
}
|
||||||
f = nil // Don't close the file.
|
f = nil // Don't close the file.
|
||||||
for i, g := range vfsShmFiles {
|
for i, g := range vfsShmFiles {
|
||||||
if g == nil {
|
if g == nil {
|
||||||
vfsShmFiles[i] = s.vfsShmFile
|
vfsShmFiles[i] = s.vfsShmFile
|
||||||
return rc
|
return _OK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vfsShmFiles = append(vfsShmFiles, s.vfsShmFile)
|
vfsShmFiles = append(vfsShmFiles, s.vfsShmFile)
|
||||||
return rc
|
return _OK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
|
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
|
||||||
|
@ -177,32 +141,22 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
|
||||||
if !extend {
|
if !extend {
|
||||||
return 0, _OK
|
return 0, _OK
|
||||||
}
|
}
|
||||||
err := osAllocate(s.File, n)
|
if osAllocate(s.File, n) != nil {
|
||||||
if err != nil {
|
|
||||||
return 0, _IOERR_SHMSIZE
|
return 0, _IOERR_SHMSIZE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var prot int
|
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, false)
|
||||||
if s.readOnly {
|
|
||||||
prot = unix.PROT_READ
|
|
||||||
} else {
|
|
||||||
prot = unix.PROT_READ | unix.PROT_WRITE
|
|
||||||
}
|
|
||||||
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, _IOERR_SHMMAP
|
return 0, _IOERR_SHMMAP
|
||||||
}
|
}
|
||||||
s.regions = append(s.regions, r)
|
s.regions = append(s.regions, r)
|
||||||
if s.readOnly {
|
|
||||||
return r.Ptr, _READONLY
|
|
||||||
}
|
|
||||||
return r.Ptr, _OK
|
return r.Ptr, _OK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||||
s.lockMtx.Lock()
|
s.Lock()
|
||||||
defer s.lockMtx.Unlock()
|
defer s.Unlock()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case flags&_SHM_UNLOCK != 0:
|
case flags&_SHM_UNLOCK != 0:
|
||||||
|
@ -224,7 +178,7 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||||
if s.lock[i] {
|
if s.lock[i] {
|
||||||
panic(util.AssertErr())
|
panic(util.AssertErr())
|
||||||
}
|
}
|
||||||
if s.vfsShmFile.lock[i] < 0 {
|
if s.vfsShmFile.lock[i]+1 <= 0 {
|
||||||
return _BUSY
|
return _BUSY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,8 +215,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
for _, r := range s.regions {
|
for _, r := range s.regions {
|
||||||
r.Unmap()
|
r.Unmap()
|
||||||
}
|
}
|
||||||
clear(s.regions)
|
s.regions = nil
|
||||||
s.regions = s.regions[:0]
|
|
||||||
|
|
||||||
// Close the file.
|
// Close the file.
|
||||||
if delete {
|
if delete {
|
||||||
|
@ -272,7 +225,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *vfsShm) shmBarrier() {
|
func (s *vfsShm) shmBarrier() {
|
||||||
s.lockMtx.Lock()
|
s.Lock()
|
||||||
//lint:ignore SA2001 memory barrier.
|
//lint:ignore SA2001 memory barrier.
|
||||||
s.lockMtx.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
//go:build (windows && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_dotlk
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_WALINDEX_HDR_SIZE = 136
|
||||||
|
_WALINDEX_PGSZ = 32768
|
||||||
|
)
|
||||||
|
|
||||||
|
// This looks like a safe way of keeping the WAL-index in sync.
|
||||||
|
//
|
||||||
|
// The WAL-index file starts with a header,
|
||||||
|
// and the index doesn't meaningfully change if the header doesn't change.
|
||||||
|
//
|
||||||
|
// The header starts with two 48 byte, checksummed, copies of the same information,
|
||||||
|
// which are accessed independently between memory barriers.
|
||||||
|
// The checkpoint information that follows uses 4 byte aligned words.
|
||||||
|
//
|
||||||
|
// Finally, we have the WAL-index hash tables,
|
||||||
|
// which are only modified holding the exclusive WAL_WRITE_LOCK.
|
||||||
|
//
|
||||||
|
// Since all the data is either redundant+checksummed,
|
||||||
|
// 4 byte aligned, or modified under an exclusive lock,
|
||||||
|
// the copies below should correctly keep copies in sync.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/walformat.html#the_wal_index_file_format
|
||||||
|
|
||||||
|
func (s *vfsShm) shmAcquire() {
|
||||||
|
if len(s.ptrs) == 0 || shmUnmodified(s.shadow[0][:], s.shared[0][:]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Copies modified words from shared to private memory.
|
||||||
|
for id, p := range s.ptrs {
|
||||||
|
shared := shmPage(s.shared[id][:])
|
||||||
|
shadow := shmPage(s.shadow[id][:])
|
||||||
|
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
|
||||||
|
for i, shared := range shared {
|
||||||
|
if shadow[i] != shared {
|
||||||
|
shadow[i] = shared
|
||||||
|
privat[i] = shared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmRelease() {
|
||||||
|
if len(s.ptrs) == 0 || shmUnmodified(s.shadow[0][:], util.View(s.mod, s.ptrs[0], _WALINDEX_HDR_SIZE)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Copies modified words from private to shared memory.
|
||||||
|
for id, p := range s.ptrs {
|
||||||
|
shared := shmPage(s.shared[id][:])
|
||||||
|
shadow := shmPage(s.shadow[id][:])
|
||||||
|
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
|
||||||
|
for i, privat := range privat {
|
||||||
|
if shadow[i] != privat {
|
||||||
|
shadow[i] = privat
|
||||||
|
shared[i] = privat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmBarrier() {
|
||||||
|
s.Lock()
|
||||||
|
s.shmAcquire()
|
||||||
|
s.shmRelease()
|
||||||
|
s.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func shmPage(s []byte) *[_WALINDEX_PGSZ / 4]uint32 {
|
||||||
|
p := (*uint32)(unsafe.Pointer(unsafe.SliceData(s)))
|
||||||
|
return (*[_WALINDEX_PGSZ / 4]uint32)(unsafe.Slice(p, _WALINDEX_PGSZ/4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func shmUnmodified(v1, v2 []byte) bool {
|
||||||
|
return *(*[_WALINDEX_HDR_SIZE]byte)(v1[:]) == *(*[_WALINDEX_HDR_SIZE]byte)(v2[:])
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
//go:build sqlite3_dotlk
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vfsShmBuffer struct {
|
||||||
|
shared [][_WALINDEX_PGSZ]byte
|
||||||
|
refs int // +checklocks:vfsShmBuffersMtx
|
||||||
|
|
||||||
|
lock [_SHM_NLOCK]int16 // +checklocks:Mutex
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// +checklocks:vfsShmBuffersMtx
|
||||||
|
vfsShmBuffers = map[string]*vfsShmBuffer{}
|
||||||
|
vfsShmBuffersMtx sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type vfsShm struct {
|
||||||
|
*vfsShmBuffer
|
||||||
|
mod api.Module
|
||||||
|
alloc api.Function
|
||||||
|
free api.Function
|
||||||
|
path string
|
||||||
|
shadow [][_WALINDEX_PGSZ]byte
|
||||||
|
ptrs []uint32
|
||||||
|
stack [1]uint64
|
||||||
|
lock [_SHM_NLOCK]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) Close() error {
|
||||||
|
if s.vfsShmBuffer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vfsShmBuffersMtx.Lock()
|
||||||
|
defer vfsShmBuffersMtx.Unlock()
|
||||||
|
|
||||||
|
// Unlock everything.
|
||||||
|
s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
|
||||||
|
|
||||||
|
// Decrease reference count.
|
||||||
|
if s.vfsShmBuffer.refs > 0 {
|
||||||
|
s.vfsShmBuffer.refs--
|
||||||
|
s.vfsShmBuffer = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.Remove(s.path)
|
||||||
|
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
delete(vfsShmBuffers, s.path)
|
||||||
|
s.vfsShmBuffer = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||||
|
if s.vfsShmBuffer != nil {
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
vfsShmBuffersMtx.Lock()
|
||||||
|
defer vfsShmBuffersMtx.Unlock()
|
||||||
|
|
||||||
|
// Find a shared buffer, increase the reference count.
|
||||||
|
if g, ok := vfsShmBuffers[s.path]; ok {
|
||||||
|
s.vfsShmBuffer = g
|
||||||
|
g.refs++
|
||||||
|
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)
|
||||||
|
if errors.Is(err, fs.ErrExist) {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new shared buffer.
|
||||||
|
s.vfsShmBuffer = &vfsShmBuffer{}
|
||||||
|
vfsShmBuffers[s.path] = s.vfsShmBuffer
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
|
||||||
|
if size != _WALINDEX_PGSZ {
|
||||||
|
return 0, _IOERR_SHMMAP
|
||||||
|
}
|
||||||
|
if s.mod == nil {
|
||||||
|
s.mod = mod
|
||||||
|
s.free = mod.ExportedFunction("sqlite3_free")
|
||||||
|
s.alloc = mod.ExportedFunction("sqlite3_malloc64")
|
||||||
|
}
|
||||||
|
if rc := s.shmOpen(); rc != _OK {
|
||||||
|
return 0, rc
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
defer s.shmAcquire()
|
||||||
|
|
||||||
|
// Extend shared memory.
|
||||||
|
if int(id) >= len(s.shared) {
|
||||||
|
if !extend {
|
||||||
|
return 0, _OK
|
||||||
|
}
|
||||||
|
s.shared = append(s.shared, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shared)+1)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate shadow memory.
|
||||||
|
if int(id) >= len(s.shadow) {
|
||||||
|
s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...)
|
||||||
|
s.shadow[0][4] = 1 // force invalidation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate local memory.
|
||||||
|
for int(id) >= len(s.ptrs) {
|
||||||
|
s.stack[0] = uint64(size)
|
||||||
|
if err := s.alloc.CallWithStack(ctx, s.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if s.stack[0] == 0 {
|
||||||
|
panic(util.OOMErr)
|
||||||
|
}
|
||||||
|
clear(util.View(s.mod, uint32(s.stack[0]), _WALINDEX_PGSZ))
|
||||||
|
s.ptrs = append(s.ptrs, uint32(s.stack[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ptrs[id], _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case flags&_SHM_LOCK != 0:
|
||||||
|
defer s.shmAcquire()
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
s.shmRelease()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case flags&_SHM_UNLOCK != 0:
|
||||||
|
for i := offset; i < offset+n; i++ {
|
||||||
|
if s.lock[i] {
|
||||||
|
if s.vfsShmBuffer.lock[i] == 0 {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
if s.vfsShmBuffer.lock[i] <= 0 {
|
||||||
|
s.vfsShmBuffer.lock[i] = 0
|
||||||
|
} else {
|
||||||
|
s.vfsShmBuffer.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.vfsShmBuffer.lock[i]+1 <= 0 {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := offset; i < offset+n; i++ {
|
||||||
|
s.vfsShmBuffer.lock[i]++
|
||||||
|
s.lock[i] = true
|
||||||
|
}
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
for i := offset; i < offset+n; i++ {
|
||||||
|
if s.lock[i] {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
if s.vfsShmBuffer.lock[i] != 0 {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := offset; i < offset+n; i++ {
|
||||||
|
s.vfsShmBuffer.lock[i] = -1
|
||||||
|
s.lock[i] = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
|
if s.vfsShmBuffer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
s.Lock()
|
||||||
|
s.shmRelease()
|
||||||
|
defer s.Unlock()
|
||||||
|
|
||||||
|
for _, p := range s.ptrs {
|
||||||
|
s.stack[0] = uint64(p)
|
||||||
|
if err := s.free.CallWithStack(context.Background(), s.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ptrs = nil
|
||||||
|
s.shadow = nil
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
//go:build (linux || darwin) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_flock || sqlite3_dotlk || sqlite3_nosys)
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vfsShm struct {
|
||||||
|
*os.File
|
||||||
|
path string
|
||||||
|
regions []*util.MappedRegion
|
||||||
|
readOnly bool
|
||||||
|
blocking bool
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ blockingSharedMemory = &vfsShm{}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||||
|
if s.File == nil {
|
||||||
|
f, err := os.OpenFile(s.path,
|
||||||
|
unix.O_RDWR|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
|
||||||
|
if err != nil {
|
||||||
|
f, err = os.OpenFile(s.path,
|
||||||
|
unix.O_RDONLY|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
|
||||||
|
s.readOnly = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return _CANTOPEN
|
||||||
|
}
|
||||||
|
s.File = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dead man's switch.
|
||||||
|
if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
} else if lock == unix.F_WRLCK {
|
||||||
|
return _BUSY
|
||||||
|
} else if lock == unix.F_UNLCK {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if err := s.Truncate(0); err != nil {
|
||||||
|
return _IOERR_SHMOPEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return osReadLock(s.File, _SHM_DMS, 1, time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
|
||||||
|
// Ensure size is a multiple of the OS page size.
|
||||||
|
if int(size)&(unix.Getpagesize()-1) != 0 {
|
||||||
|
return 0, _IOERR_SHMMAP
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc := s.shmOpen(); rc != _OK {
|
||||||
|
return 0, rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file is big enough.
|
||||||
|
o, err := s.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _IOERR_SHMSIZE
|
||||||
|
}
|
||||||
|
if n := (int64(id) + 1) * int64(size); n > o {
|
||||||
|
if !extend {
|
||||||
|
return 0, _OK
|
||||||
|
}
|
||||||
|
if s.readOnly || osAllocate(s.File, n) != nil {
|
||||||
|
return 0, _IOERR_SHMSIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, s.readOnly)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _IOERR_SHMMAP
|
||||||
|
}
|
||||||
|
s.regions = append(s.regions, r)
|
||||||
|
if s.readOnly {
|
||||||
|
return r.Ptr, _READONLY
|
||||||
|
}
|
||||||
|
return r.Ptr, _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||||
|
// Argument check.
|
||||||
|
if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
switch flags {
|
||||||
|
case
|
||||||
|
_SHM_LOCK | _SHM_SHARED,
|
||||||
|
_SHM_LOCK | _SHM_EXCLUSIVE,
|
||||||
|
_SHM_UNLOCK | _SHM_SHARED,
|
||||||
|
_SHM_UNLOCK | _SHM_EXCLUSIVE:
|
||||||
|
//
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
if n != 1 && flags&_SHM_EXCLUSIVE == 0 {
|
||||||
|
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), timeout)
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), timeout)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
|
if s.File == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap regions.
|
||||||
|
for _, r := range s.regions {
|
||||||
|
r.Unmap()
|
||||||
|
}
|
||||||
|
s.regions = nil
|
||||||
|
|
||||||
|
// Close the file.
|
||||||
|
if delete {
|
||||||
|
os.Remove(s.path)
|
||||||
|
}
|
||||||
|
s.Close()
|
||||||
|
s.File = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmBarrier() {
|
||||||
|
s.Lock()
|
||||||
|
//lint:ignore SA2001 memory barrier.
|
||||||
|
s.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmEnableBlocking(block bool) {
|
||||||
|
s.blocking = block
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !(darwin || linux || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || !(386 || arm || amd64 || arm64 || riscv64 || ppc64le) || sqlite3_noshm || sqlite3_nosys
|
//go:build !(((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk)
|
||||||
|
|
||||||
package vfs
|
package vfs
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
//go:build (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_dotlk || sqlite3_nosys)
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/ncruces/go-sqlite3/util/osutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vfsShm struct {
|
||||||
|
*os.File
|
||||||
|
mod api.Module
|
||||||
|
alloc api.Function
|
||||||
|
free api.Function
|
||||||
|
path string
|
||||||
|
regions []*util.MappedRegion
|
||||||
|
shared [][]byte
|
||||||
|
shadow [][_WALINDEX_PGSZ]byte
|
||||||
|
ptrs []uint32
|
||||||
|
stack [1]uint64
|
||||||
|
blocking bool
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ blockingSharedMemory = &vfsShm{}
|
||||||
|
|
||||||
|
func (s *vfsShm) Close() error {
|
||||||
|
// Unmap regions.
|
||||||
|
for _, r := range s.regions {
|
||||||
|
r.Unmap()
|
||||||
|
}
|
||||||
|
s.regions = nil
|
||||||
|
|
||||||
|
// Close the file.
|
||||||
|
return s.File.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmOpen() _ErrorCode {
|
||||||
|
if s.File == nil {
|
||||||
|
f, err := osutil.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return _CANTOPEN
|
||||||
|
}
|
||||||
|
s.File = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dead man's switch.
|
||||||
|
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc == _OK {
|
||||||
|
err := s.Truncate(0)
|
||||||
|
osUnlock(s.File, _SHM_DMS, 1)
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_SHMOPEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return osReadLock(s.File, _SHM_DMS, 1, time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) {
|
||||||
|
// Ensure size is a multiple of the OS page size.
|
||||||
|
if size != _WALINDEX_PGSZ || (windows.Getpagesize()-1)&_WALINDEX_PGSZ != 0 {
|
||||||
|
return 0, _IOERR_SHMMAP
|
||||||
|
}
|
||||||
|
if s.mod == nil {
|
||||||
|
s.mod = mod
|
||||||
|
s.free = mod.ExportedFunction("sqlite3_free")
|
||||||
|
s.alloc = mod.ExportedFunction("sqlite3_malloc64")
|
||||||
|
}
|
||||||
|
if rc := s.shmOpen(); rc != _OK {
|
||||||
|
return 0, rc
|
||||||
|
}
|
||||||
|
|
||||||
|
defer s.shmAcquire()
|
||||||
|
|
||||||
|
// Check if file is big enough.
|
||||||
|
o, err := s.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _IOERR_SHMSIZE
|
||||||
|
}
|
||||||
|
if n := (int64(id) + 1) * int64(size); n > o {
|
||||||
|
if !extend {
|
||||||
|
return 0, _OK
|
||||||
|
}
|
||||||
|
if osAllocate(s.File, n) != nil {
|
||||||
|
return 0, _IOERR_SHMSIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps regions into memory.
|
||||||
|
for int(id) >= len(s.shared) {
|
||||||
|
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _IOERR_SHMMAP
|
||||||
|
}
|
||||||
|
s.regions = append(s.regions, r)
|
||||||
|
s.shared = append(s.shared, r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate shadow memory.
|
||||||
|
if int(id) >= len(s.shadow) {
|
||||||
|
s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...)
|
||||||
|
s.shadow[0][4] = 1 // force invalidation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate local memory.
|
||||||
|
for int(id) >= len(s.ptrs) {
|
||||||
|
s.stack[0] = uint64(size)
|
||||||
|
if err := s.alloc.CallWithStack(ctx, s.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if s.stack[0] == 0 {
|
||||||
|
panic(util.OOMErr)
|
||||||
|
}
|
||||||
|
clear(util.View(s.mod, uint32(s.stack[0]), _WALINDEX_PGSZ))
|
||||||
|
s.ptrs = append(s.ptrs, uint32(s.stack[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.ptrs[id], _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||||
|
switch {
|
||||||
|
case flags&_SHM_LOCK != 0:
|
||||||
|
defer s.shmAcquire()
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
s.shmRelease()
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout time.Duration
|
||||||
|
if s.blocking {
|
||||||
|
timeout = time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case flags&_SHM_UNLOCK != 0:
|
||||||
|
return osUnlock(s.File, _SHM_BASE+uint32(offset), uint32(n))
|
||||||
|
case flags&_SHM_SHARED != 0:
|
||||||
|
return osReadLock(s.File, _SHM_BASE+uint32(offset), uint32(n), timeout)
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
return osWriteLock(s.File, _SHM_BASE+uint32(offset), uint32(n), timeout)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
|
if s.File == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.shmRelease()
|
||||||
|
|
||||||
|
// Free local memory.
|
||||||
|
for _, p := range s.ptrs {
|
||||||
|
s.stack[0] = uint64(p)
|
||||||
|
if err := s.free.CallWithStack(context.Background(), s.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.ptrs = nil
|
||||||
|
s.shadow = nil
|
||||||
|
s.shared = nil
|
||||||
|
|
||||||
|
// Close the file.
|
||||||
|
s.Close()
|
||||||
|
s.File = nil
|
||||||
|
if delete {
|
||||||
|
os.Remove(s.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmEnableBlocking(block bool) {
|
||||||
|
s.blocking = block
|
||||||
|
}
|
|
@ -514,7 +514,7 @@ github.com/modern-go/reflect2
|
||||||
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
|
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
|
||||||
## explicit
|
## explicit
|
||||||
github.com/munnerz/goautoneg
|
github.com/munnerz/goautoneg
|
||||||
# github.com/ncruces/go-sqlite3 v0.20.0
|
# github.com/ncruces/go-sqlite3 v0.20.2
|
||||||
## explicit; go 1.21
|
## explicit; go 1.21
|
||||||
github.com/ncruces/go-sqlite3
|
github.com/ncruces/go-sqlite3
|
||||||
github.com/ncruces/go-sqlite3/driver
|
github.com/ncruces/go-sqlite3/driver
|
||||||
|
|
Loading…
Reference in New Issue