Compare commits

...

8 Commits

Author SHA1 Message Date
Nuno Cruces
37a3ff37e8 wazero 1.0. 2023-03-24 21:17:30 +00:00
Nuno Cruces
d880d6842c Refactor VFS. 2023-03-23 13:29:26 +00:00
Nuno Cruces
bef46e7954 Locking improvements (windows). 2023-03-23 12:40:55 +00:00
Nuno Cruces
4e72b4d117 Locking fix. 2023-03-23 11:26:19 +00:00
Nuno Cruces
3b08d02a83 Lock refactoring. 2023-03-23 01:55:54 +00:00
Nuno Cruces
b19c12c4c7 SQLite 3.41.2, prefer speed over size. 2023-03-23 00:44:43 +00:00
Nuno Cruces
859a21ef4e CI improvements. 2023-03-22 12:08:33 +00:00
Nuno Cruces
8ff0ee752f Use flock. 2023-03-22 03:15:54 +00:00
28 changed files with 393 additions and 291 deletions

View File

@@ -18,11 +18,27 @@ jobs:
with:
lfs: 'true'
- name: Set up Go
uses: actions/setup-go@v3
- name: Set up
uses: actions/setup-go@v4
with:
go-version: stable
cache: true
- name: Format
run: gofmt -s -w . && git diff --exit-code
if: matrix.os != 'windows-latest'
- name: Tidy
run: go mod tidy && git diff --exit-code
- name: Download
run: go mod download
- name: Verify
run: go mod verify
- name: Vet
run: go vet ./...
continue-on-error: true
- name: Build
run: go build -v ./...
@@ -34,10 +50,15 @@ jobs:
run: go test -v -race ./...
if: matrix.os == 'ubuntu-latest'
- name: Update coverage report
- name: Test BSD locks
run: go test -v -tags sqlite3_bsd ./...
if: matrix.os == 'macos-latest'
- name: Coverage report
uses: ncruces/go-coverage-report@main
with:
chart: 'true'
amend: 'true'
if: |
matrix.os == 'ubuntu-latest' &&
github.event_name == 'push'

View File

@@ -39,22 +39,25 @@ To open WAL databases, or use `EXCLUSIVE` locking mode,
disable connection pooling by calling
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
#### Open File Description Locks
#### POSIX Advisory Locks
On Unix, this module uses [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
POSIX advisory locks, which SQLite uses, are
[broken by design](https://www.sqlite.org/src/artifact/90c4fa?ln=1073-1161).
On Linux, macOS and illumos, this module uses
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
to synchronize access to database files.
OFD locks are fully compatible with process-associated POSIX advisory locks.
POSIX advisory locks, which SQLite uses, are [broken by design](https://www.sqlite.org/src/artifact/90c4fa?ln=1073-1161).
OFD locks are fully compatible with process-associated POSIX advisory locks,
and are supported on Linux and macOS.
As a work around for other Unixes, you can use [`nolock=1`](https://www.sqlite.org/uri.html).
On BSD Unixes, this module uses
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
BSD locks may _not_ be compatible with process-associated POSIX advisory locks.
#### Testing
The pure Go VFS is stress tested by running an unmodified build of SQLite's
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c)
on Linux, macOS and Windows.
Performance is tested by running
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
@@ -64,18 +67,14 @@ Performance is tested by running
- [x] nested transactions
- [x] incremental BLOB I/O
- [x] online backup
- [ ] snapshots
- [ ] session extension
- [ ] resumable bulk update
- [ ] shared-cache mode
- [ ] unlock-notify
- [ ] custom SQL functions
- [ ] custom VFSes
- [ ] in-memory VFS
- [ ] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt)
- [ ] in-memory VFS, wrapping a [`bytes.Buffer`](https://pkg.go.dev/bytes#Buffer)
- [ ] cloud-based VFS, based on [Cloud Backed SQLite](https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki)
- [ ] custom VFS API
### Alternatives
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)

View File

@@ -1,6 +1,6 @@
# Embeddable WASM build of SQLite
This folder includes an embeddable WASM build of SQLite 3.41.1 for use with
This folder includes an embeddable WASM build of SQLite 3.41.2 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:

View File

@@ -4,17 +4,12 @@ set -eo pipefail
cd -P -- "$(dirname -- "$0")"
# build SQLite
zig cc --target=wasm32-wasi -flto -g0 -Os \
zig cc --target=wasm32-wasi -flto -g0 -O2 \
-o sqlite3.wasm ../sqlite3/main.c \
-I../sqlite3/ \
-mmutable-globals \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
-mexec-model=reactor \
-D_HAVE_SQLITE_CONFIG_H \
$(awk '{print "-Wl,--export="$0}' exports.txt)
# optimize SQLite
if which wasm-opt; then
wasm-opt -g -O -o sqlite3.tmp sqlite3.wasm
mv sqlite3.tmp sqlite3.wasm
fi
$(awk '{print "-Wl,--export="$0}' exports.txt)

BIN
embed/sqlite3.wasm Normal file → Executable file

Binary file not shown.

View File

@@ -202,7 +202,6 @@ const (
noFuncErr = errorString("sqlite3: could not find function: ")
binaryErr = errorString("sqlite3: no SQLite binary embed/set/loaded")
timeErr = errorString("sqlite3: invalid time value")
notImplErr = errorString("sqlite3: not implemented")
whenceErr = errorString("sqlite3: invalid whence")
offsetErr = errorString("sqlite3: invalid offset")
)

2
go.mod
View File

@@ -4,7 +4,7 @@ go 1.19
require (
github.com/ncruces/julianday v0.1.5
github.com/tetratelabs/wazero v1.0.0-rc.2
github.com/tetratelabs/wazero v1.0.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.6.0
)

4
go.sum
View File

@@ -1,7 +1,7 @@
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.0.0-rc.2 h1:OA3UUynnoqxrjCQ94mpAtdO4/oMxFQVNL2BXDMOc66Q=
github.com/tetratelabs/wazero v1.0.0-rc.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
github.com/tetratelabs/wazero v1.0.0 h1:sCE9+mjFex95Ki6hdqwvhyF25x5WslADjDKIFU5BXzI=
github.com/tetratelabs/wazero v1.0.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=

View File

@@ -3,14 +3,10 @@ package sqlite3
import (
"context"
"crypto/rand"
"io"
"math"
"os"
"runtime"
"strconv"
"sync"
"sync/atomic"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
@@ -28,11 +24,10 @@ var (
)
var sqlite3 struct {
once sync.Once
runtime wazero.Runtime
compiled wazero.CompiledModule
instances atomic.Uint64
err error
once sync.Once
runtime wazero.Runtime
compiled wazero.CompiledModule
err error
}
func instantiateModule() (*module, error) {
@@ -43,12 +38,7 @@ func instantiateModule() (*module, error) {
return nil, sqlite3.err
}
name := "sqlite3-" + strconv.FormatUint(sqlite3.instances.Add(1), 10)
cfg := wazero.NewModuleConfig().WithName(name).
WithSysWalltime().WithSysNanotime().WithSysNanosleep().
WithOsyield(runtime.Gosched).
WithRandSource(rand.Reader)
cfg := wazero.NewModuleConfig().WithStartFunctions("_initialize")
mod, err := sqlite3.runtime.InstantiateModule(ctx, sqlite3.compiled, cfg)
if err != nil {

View File

@@ -3,29 +3,29 @@ set -eo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://sqlite.org/2023/sqlite-amalgamation-3410100.zip"
curl -#OL "https://sqlite.org/2023/sqlite-amalgamation-3410200.zip"
unzip -d . sqlite-amalgamation-*.zip
mv sqlite-amalgamation-*/sqlite3* .
rm -rf sqlite-amalgamation-*
cd ext/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/ext/misc/uuid.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/ext/misc/series.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/ext/misc/decimal.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/ext/misc/uint.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/ext/misc/uuid.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/ext/misc/base64.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/ext/misc/regexp.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/ext/misc/series.c"
cd ~-
cd ../tests/mptest/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/mptest/multiwrite01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/mptest/mptest.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/mptest/config01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/mptest/config02.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/mptest/crash01.test"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/mptest/crash02.subtest"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/mptest/multiwrite01.test"
cd ~-
cd ../tests/speedtest1/testdata/
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.1/test/speedtest1.c"
curl -#OL "https://github.com/sqlite/sqlite/raw/version-3.41.2/test/speedtest1.c"
cd ~-

View File

@@ -21,28 +21,13 @@ int sqlite3_os_init() {
return sqlite3_vfs_register(os_vfs(), /*default=*/true);
}
int main() {
int rc = sqlite3_initialize();
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_base_init);
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_decimal_init);
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_regexp_init);
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_series_init);
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_uint_init);
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_uuid_init);
if (rc != SQLITE_OK) return 1;
rc = sqlite3_auto_extension((void (*)(void))sqlite3_time_init);
if (rc != SQLITE_OK) return 1;
__attribute__((constructor)) void premain() {
sqlite3_initialize();
sqlite3_auto_extension((void (*)(void))sqlite3_base_init);
sqlite3_auto_extension((void (*)(void))sqlite3_decimal_init);
sqlite3_auto_extension((void (*)(void))sqlite3_regexp_init);
sqlite3_auto_extension((void (*)(void))sqlite3_series_init);
sqlite3_auto_extension((void (*)(void))sqlite3_uint_init);
sqlite3_auto_extension((void (*)(void))sqlite3_uuid_init);
sqlite3_auto_extension((void (*)(void))sqlite3_time_init);
}

View File

@@ -2,7 +2,7 @@
#include "sqlite3.h"
int os_localtime(sqlite3_int64, struct tm *);
int os_localtime(struct tm *, sqlite3_int64);
int os_randomness(sqlite3_vfs *, int nByte, char *zOut);
int os_sleep(sqlite3_vfs *, int microseconds);
@@ -140,5 +140,5 @@ sqlite3_vfs *os_vfs() {
}
int localtime_s(struct tm *const pTm, time_t const *const pTime) {
return os_localtime((sqlite3_int64)*pTime, pTm);
return os_localtime(pTm, (sqlite3_int64)*pTime);
}

View File

@@ -3,7 +3,7 @@ set -eo pipefail
cd -P -- "$(dirname -- "$0")"
zig cc --target=wasm32-wasi -flto -g0 -Os \
zig cc --target=wasm32-wasi -flto -g0 -O2 \
-o mptest.wasm main.c \
-I../../../sqlite3/ \
-mmutable-globals \
@@ -14,9 +14,4 @@ zig cc --target=wasm32-wasi -flto -g0 -Os \
-DSQLITE_DEFAULT_LOCKING_MODE=0 \
-DHAVE_USLEEP -DSQLITE_NO_SYNC \
-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid
if which wasm-opt; then
wasm-opt -g -O -o mptest.tmp mptest.wasm
mv mptest.tmp mptest.wasm
fi
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid

4
tests/mptest/testdata/mptest.wasm vendored Normal file → Executable file
View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:af307c3555fcf5f78e07a079d8f099400c3e013508f30f5efbe3b7f259be2092
size 969309
oid sha256:f81ce390812d944d1fa9b2cc607a3629febab0bc0e4473dad3170134509c1751
size 1630826

View File

@@ -3,15 +3,10 @@ set -eo pipefail
cd -P -- "$(dirname -- "$0")"
zig cc --target=wasm32-wasi -flto -g0 -Os \
zig cc --target=wasm32-wasi -flto -g0 -O2 \
-o speedtest1.wasm main.c \
-I../../../sqlite3/ \
-mmutable-globals \
-mbulk-memory -mreference-types \
-mnontrapping-fptoint -msign-ext \
-D_HAVE_SQLITE_CONFIG_H
if which wasm-opt; then
wasm-opt -g -O -o speedtest1.tmp speedtest1.wasm
mv speedtest1.tmp speedtest1.wasm
fi
-D_HAVE_SQLITE_CONFIG_H

4
tests/speedtest1/testdata/speedtest1.wasm vendored Normal file → Executable file
View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2058b59b052ce9e3ed74c241f06122e8acfe0b5d4a18f36130e625b34eecd161
size 1001469
oid sha256:0e02a26b86832a4703cd2a86c98ff8041d9d889b865a61f324b512a15d2d361e
size 1676129

40
vfs.go
View File

@@ -14,19 +14,11 @@ import (
"github.com/ncruces/julianday"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/sys"
)
func vfsInstantiate(ctx context.Context, r wazero.Runtime) {
wasi := r.NewHostModuleBuilder("wasi_snapshot_preview1")
vfsRegisterFunc(wasi, "proc_exit", vfsExit)
_, err := wasi.Instantiate(ctx)
if err != nil {
panic(err)
}
env := vfsNewEnvModuleBuilder(r)
_, err = env.Instantiate(ctx)
_, err := env.Instantiate(ctx)
if err != nil {
panic(err)
}
@@ -34,7 +26,7 @@ func vfsInstantiate(ctx context.Context, r wazero.Runtime) {
func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
env := r.NewHostModuleBuilder("env")
vfsRegisterFunc(env, "os_localtime", vfsLocaltime)
vfsRegisterFuncT(env, "os_localtime", vfsLocaltime)
vfsRegisterFunc3(env, "os_randomness", vfsRandomness)
vfsRegisterFunc2(env, "os_sleep", vfsSleep)
vfsRegisterFunc2(env, "os_current_time", vfsCurrentTime)
@@ -46,7 +38,7 @@ func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
vfsRegisterFunc1(env, "os_close", vfsClose)
vfsRegisterFuncRW(env, "os_read", vfsRead)
vfsRegisterFuncRW(env, "os_write", vfsWrite)
vfsRegisterFunc(env, "os_truncate", vfsTruncate)
vfsRegisterFuncT(env, "os_truncate", vfsTruncate)
vfsRegisterFunc2(env, "os_sync", vfsSync)
vfsRegisterFunc2(env, "os_file_size", vfsFileSize)
vfsRegisterFunc2(env, "os_lock", vfsLock)
@@ -87,14 +79,7 @@ func (vfs *vfsState) Close() error {
return nil
}
func vfsExit(ctx context.Context, mod api.Module, exitCode uint32) {
// Ensure other callers see the exit code.
_ = mod.CloseWithExitCode(ctx, exitCode)
// Prevent any code from executing after this function.
panic(sys.NewExitError(mod.Name(), exitCode))
}
func vfsLocaltime(ctx context.Context, mod api.Module, t uint64, pTm uint32) uint32 {
func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t uint64) uint32 {
tm := time.Unix(int64(t), 0)
var isdst int
if tm.IsDST() {
@@ -338,9 +323,6 @@ func vfsSizeHint(ctx context.Context, mod api.Module, pFile, pArg uint32) uint32
file := vfsFile.GetOS(ctx, mod, pFile)
size := memory{mod}.readUint64(pArg)
err := vfsOS.Allocate(file, int64(size))
if err == notImplErr {
return uint32(NOTFOUND)
}
if err != nil {
return uint32(IOERR_TRUNCATE)
}
@@ -365,10 +347,6 @@ func vfsFileMoved(ctx context.Context, mod api.Module, pFile, pResOut uint32) ui
return _OK
}
func vfsRegisterFunc(mod wazero.HostModuleBuilder, name string, fn any) {
mod.NewFunctionBuilder().WithFunc(fn).Export(name)
}
func vfsRegisterFunc1(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ uint32) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
@@ -428,3 +406,13 @@ func vfsRegisterFuncRW(mod wazero.HostModuleBuilder, name string, fn func(ctx co
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}
func vfsRegisterFuncT(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ uint32, _ uint64) uint32) {
mod.NewFunctionBuilder().
WithGoModuleFunction(api.GoModuleFunc(
func(ctx context.Context, mod api.Module, stack []uint64) {
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), stack[1]))
}),
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
Export(name)
}

View File

@@ -89,10 +89,6 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
if cLock != _NO_LOCK {
panic(assertErr())
}
// Test the PENDING lock before acquiring a new SHARED lock.
if locked, _ := vfsOS.CheckPendingLock(file); locked {
return uint32(BUSY)
}
if rc := vfsOS.GetSharedLock(file, timeout); rc != _OK {
return uint32(rc)
}
@@ -172,8 +168,21 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 {
file := vfsFile.GetOS(ctx, mod, pFile)
cLock := vfsFile.GetLock(ctx, mod, pFile)
// Connection state check.
if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK {
panic(assertErr())
}
var locked bool
var rc xErrorCode
if cLock >= _RESERVED_LOCK {
locked = true
} else {
locked, rc = vfsOS.CheckReservedLock(file)
}
locked, rc := vfsOS.CheckReservedLock(file)
var res uint32
if locked {
res = 1
@@ -182,11 +191,6 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
return uint32(rc)
}
func (vfsOSMethods) GetSharedLock(file *os.File, timeout time.Duration) xErrorCode {
// Acquire the SHARED lock.
return vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
}
func (vfsOSMethods) GetReservedLock(file *os.File, timeout time.Duration) xErrorCode {
// Acquire the RESERVED lock.
return vfsOS.writeLock(file, _RESERVED_BYTE, 1, timeout)
@@ -201,8 +205,3 @@ func (vfsOSMethods) CheckReservedLock(file *os.File) (bool, xErrorCode) {
// Test the RESERVED lock.
return vfsOS.checkLock(file, _RESERVED_BYTE, 1)
}
func (vfsOSMethods) CheckPendingLock(file *os.File) (bool, xErrorCode) {
// Test the PENDING lock.
return vfsOS.checkLock(file, _PENDING_BYTE, 1)
}

View File

@@ -51,6 +51,13 @@ func Test_vfsLock(t *testing.T) {
if got := mem.readUint32(pOutput); got != 0 {
t.Error("file was locked")
}
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput)
if rc != _OK {
t.Fatal("returned", rc)
}
if got := mem.readUint32(pOutput); got != 0 {
t.Error("file was locked")
}
rc = vfsLock(ctx, mem.mod, pFile2, _SHARED_LOCK)
if rc != _OK {
@@ -64,6 +71,13 @@ func Test_vfsLock(t *testing.T) {
if got := mem.readUint32(pOutput); got != 0 {
t.Error("file was locked")
}
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput)
if rc != _OK {
t.Fatal("returned", rc)
}
if got := mem.readUint32(pOutput); got != 0 {
t.Error("file was locked")
}
rc = vfsLock(ctx, mem.mod, pFile2, _RESERVED_LOCK)
if rc != _OK {
@@ -81,6 +95,13 @@ func Test_vfsLock(t *testing.T) {
if got := mem.readUint32(pOutput); got == 0 {
t.Error("file wasn't locked")
}
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput)
if rc != _OK {
t.Fatal("returned", rc)
}
if got := mem.readUint32(pOutput); got == 0 {
t.Error("file wasn't locked")
}
rc = vfsLock(ctx, mem.mod, pFile2, _EXCLUSIVE_LOCK)
if rc != _OK {
@@ -94,6 +115,13 @@ func Test_vfsLock(t *testing.T) {
if got := mem.readUint32(pOutput); got == 0 {
t.Error("file wasn't locked")
}
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput)
if rc != _OK {
t.Fatal("returned", rc)
}
if got := mem.readUint32(pOutput); got == 0 {
t.Error("file wasn't locked")
}
rc = vfsLock(ctx, mem.mod, pFile1, _SHARED_LOCK)
if rc == _OK {
@@ -107,6 +135,13 @@ func Test_vfsLock(t *testing.T) {
if got := mem.readUint32(pOutput); got == 0 {
t.Error("file wasn't locked")
}
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput)
if rc != _OK {
t.Fatal("returned", rc)
}
if got := mem.readUint32(pOutput); got == 0 {
t.Error("file wasn't locked")
}
rc = vfsUnlock(ctx, mem.mod, pFile2, _SHARED_LOCK)
if rc != _OK {
@@ -120,6 +155,13 @@ func Test_vfsLock(t *testing.T) {
if got := mem.readUint32(pOutput); got != 0 {
t.Error("file was locked")
}
rc = vfsCheckReservedLock(ctx, mem.mod, pFile2, pOutput)
if rc != _OK {
t.Fatal("returned", rc)
}
if got := mem.readUint32(pOutput); got != 0 {
t.Error("file was locked")
}
rc = vfsLock(ctx, mem.mod, pFile1, _SHARED_LOCK)
if rc != _OK {

56
vfs_os_bsd.go Normal file
View File

@@ -0,0 +1,56 @@
//go:build freebsd || openbsd || netbsd || dragonfly || (darwin && sqlite3_bsd)
package sqlite3
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
if start == 0 && len == 0 {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return IOERR_UNLOCK
}
}
return _OK
}
func (vfsOSMethods) lock(file *os.File, how int, timeout time.Duration, def xErrorCode) xErrorCode {
var err error
for {
err = unix.Flock(int(file.Fd()), how)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, def)
}
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.LOCK_SH|unix.LOCK_NB, timeout, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.LOCK_EX|unix.LOCK_NB, timeout, IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

View File

@@ -1,20 +1,44 @@
//go:build !sqlite3_bsd
package sqlite3
import (
"io"
"os"
"time"
"golang.org/x/sys/unix"
)
const (
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
_F_OFD_SETLK = 90
_F_OFD_SETLKW = 91
_F_OFD_GETLK = 92
_F_OFD_SETLKWTIMEOUT = 93
)
type flocktimeout_t struct {
fl unix.Flock_t
timeout unix.Timespec
}
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
if !fullsync {
return unix.Fsync(int(file.Fd()))
if fullsync {
return file.Sync()
}
return file.Sync()
return unix.Fsync(int(file.Fd()))
}
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if size <= off {
return nil
}
// https://stackoverflow.com/a/11497568/867786
store := unix.Fstore_t{
Flags: unix.F_ALLOCATECONTIG,
@@ -24,37 +48,59 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error {
}
// Try to get a continous chunk of disk space.
err := unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
if err != nil {
// OK, perhaps we are too fragmented, allocate non-continuous.
store.Flags = unix.F_ALLOCATEALL
return unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
}
return nil
return file.Truncate(size)
}
func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error {
const F_OFD_GETLK = 92 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
return unix.FcntlFlock(file.Fd(), F_OFD_GETLK, lock)
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return IOERR_UNLOCK
}
return _OK
}
func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error {
const F_OFD_SETLK = 90 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
return unix.FcntlFlock(file.Fd(), F_OFD_SETLK, &lock)
}
func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error {
func (vfsOSMethods) lock(file *os.File, typ int16, start, len int64, timeout time.Duration, def xErrorCode) xErrorCode {
lock := flocktimeout_t{fl: unix.Flock_t{
Type: typ,
Start: start,
Len: len,
}}
var err error
if timeout == 0 {
return vfsOS.fcntlSetLock(file, lock)
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
} else {
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
}
const F_OFD_SETLKWTIMEOUT = 93 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
flocktimeout := &struct {
unix.Flock_t
unix.Timespec
}{
Flock_t: lock,
Timespec: unix.NsecToTimespec(int64(timeout / time.Nanosecond)),
}
return unix.FcntlFlock(file.Fd(), F_OFD_SETLKWTIMEOUT, &flocktimeout.Flock_t)
return vfsOS.lockErrorCode(err, def)
}
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.F_RDLCK, start, len, timeout, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.F_WRLCK, start, len, timeout, IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), _F_OFD_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

View File

@@ -2,14 +2,12 @@ package sqlite3
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
if dataonly {
//lint:ignore SA1019 OK on linux
_, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0)
if err != 0 {
return err
@@ -25,25 +23,3 @@ func (vfsOSMethods) Allocate(file *os.File, size int64) error {
}
return unix.Fallocate(int(file.Fd()), 0, 0, size)
}
func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error {
return unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, lock)
}
func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error {
return unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
}
func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error {
for {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
return err
}
if timeout < time.Millisecond {
return err
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
}

63
vfs_os_ofd.go Normal file
View File

@@ -0,0 +1,63 @@
//go:build linux || illumos
package sqlite3
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return IOERR_UNLOCK
}
return _OK
}
func (vfsOSMethods) lock(file *os.File, typ int16, start, len int64, timeout time.Duration, def xErrorCode) xErrorCode {
lock := unix.Flock_t{
Type: typ,
Start: start,
Len: len,
}
var err error
for {
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Millisecond {
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, def)
}
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.F_RDLCK, start, len, timeout, IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lock(file, unix.F_WRLCK, start, len, timeout, IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}

23
vfs_os_other.go Normal file
View File

@@ -0,0 +1,23 @@
//go:build !linux && (!darwin || sqlite3_bsd)
package sqlite3
import (
"io"
"os"
)
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
return file.Sync()
}
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if size <= off {
return nil
}
return file.Truncate(size)
}

View File

@@ -1,30 +0,0 @@
//go:build !windows && !linux && !darwin
package sqlite3
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
return file.Sync()
}
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
return notImplErr
}
func (vfsOSMethods) fcntlGetLock(file *os.File, lock *unix.Flock_t) error {
return notImplErr
}
func (vfsOSMethods) fcntlSetLock(file *os.File, lock unix.Flock_t) error {
return notImplErr
}
func (vfsOSMethods) fcntlSetLockTimeout(file *os.File, lock unix.Flock_t, timeout time.Duration) error {
return notImplErr
}

View File

@@ -15,7 +15,7 @@ func (vfsOSMethods) OpenFile(name string, flag int, perm fs.FileMode) (*os.File,
}
func (vfsOSMethods) Access(path string, flags _AccessFlag) error {
var access uint32 = unix.F_OK
var access uint32 // unix.F_OK
switch flags {
case _ACCESS_READWRITE:
access = unix.R_OK | unix.W_OK
@@ -25,6 +25,15 @@ func (vfsOSMethods) Access(path string, flags _AccessFlag) error {
return unix.Access(path, access)
}
func (vfsOSMethods) GetSharedLock(file *os.File, timeout time.Duration) xErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if pending, _ := vfsOS.checkLock(file, _PENDING_BYTE, 1); pending {
return xErrorCode(BUSY)
}
// Acquire the SHARED lock.
return vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
}
func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode {
if timeout == 0 {
timeout = time.Millisecond
@@ -55,47 +64,6 @@ func (vfsOSMethods) ReleaseLock(file *os.File, _ vfsLockState) xErrorCode {
return vfsOS.unlock(file, 0, 0)
}
func (vfsOSMethods) unlock(file *os.File, start, len int64) xErrorCode {
err := vfsOS.fcntlSetLock(file, unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return IOERR_UNLOCK
}
return _OK
}
func (vfsOSMethods) readLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}, timeout), IOERR_RDLOCK)
}
func (vfsOSMethods) writeLock(file *os.File, start, len int64, timeout time.Duration) xErrorCode {
// TODO: implement timeouts.
return vfsOS.lockErrorCode(vfsOS.fcntlSetLockTimeout(file, unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}, timeout), IOERR_LOCK)
}
func (vfsOSMethods) checkLock(file *os.File, start, len int64) (bool, xErrorCode) {
lock := unix.Flock_t{
Type: unix.F_RDLCK,
Start: start,
Len: len,
}
if vfsOS.fcntlGetLock(file, &lock) != nil {
return false, IOERR_CHECKRESERVEDLOCK
}
return lock.Type != unix.F_UNLCK, _OK
}
func (vfsOSMethods) lockErrorCode(err error, def xErrorCode) xErrorCode {
if err == nil {
return _OK

View File

@@ -1,7 +1,6 @@
package sqlite3
import (
"io"
"io/fs"
"os"
"syscall"
@@ -48,19 +47,18 @@ func (vfsOSMethods) Access(path string, flags _AccessFlag) error {
return nil
}
func (vfsOSMethods) Sync(file *os.File, fullsync, dataonly bool) error {
return file.Sync()
}
func (vfsOSMethods) GetSharedLock(file *os.File, timeout time.Duration) xErrorCode {
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
rc := vfsOS.readLock(file, _PENDING_BYTE, 1, timeout)
func (vfsOSMethods) Allocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
if rc == _OK {
// Acquire the SHARED lock.
rc = vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
// Release the PENDING lock.
vfsOS.unlock(file, _PENDING_BYTE, 1)
}
if size > off {
return file.Truncate(size)
}
return nil
return rc
}
func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErrorCode {
@@ -74,8 +72,8 @@ func (vfsOSMethods) GetExclusiveLock(file *os.File, timeout time.Duration) xErro
// Acquire the EXCLUSIVE lock.
rc := vfsOS.writeLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
// Reacquire the SHARED lock.
if rc != _OK {
// Reacquire the SHARED lock.
vfsOS.readLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
return rc
@@ -131,18 +129,20 @@ func (vfsOSMethods) unlock(file *os.File, start, len uint32) xErrorCode {
}
func (vfsOSMethods) lock(file *os.File, flags, start, len uint32, timeout time.Duration, def xErrorCode) xErrorCode {
var err error
for {
err := windows.LockFileEx(windows.Handle(file.Fd()), flags,
err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
0, len, 0, &windows.Overlapped{Offset: start})
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
return vfsOS.lockErrorCode(err, def)
break
}
if timeout < time.Millisecond {
return vfsOS.lockErrorCode(err, def)
break
}
timeout -= time.Millisecond
time.Sleep(time.Millisecond)
}
return vfsOS.lockErrorCode(err, def)
}
func (vfsOSMethods) readLock(file *os.File, start, len uint32, timeout time.Duration) xErrorCode {

View File

@@ -14,19 +14,11 @@ import (
"github.com/ncruces/julianday"
)
func Test_vfsExit(t *testing.T) {
mem := newMemory(128)
ctx := context.TODO()
defer func() { _ = recover() }()
vfsExit(ctx, mem.mod, 1)
t.Error("want panic")
}
func Test_vfsLocaltime(t *testing.T) {
mem := newMemory(128)
ctx := context.TODO()
rc := vfsLocaltime(ctx, mem.mod, 0, 4)
rc := vfsLocaltime(ctx, mem.mod, 4, 0)
if rc != 0 {
t.Fatal("returned", rc)
}