Cross compilation.

This commit is contained in:
Nuno Cruces
2023-10-17 14:04:23 +01:00
parent 728e59951b
commit 8d0c654178
22 changed files with 222 additions and 181 deletions

28
.github/workflows/bsd.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: BSD
on:
workflow_dispatch:
jobs:
test:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
with:
lfs: 'true'
- name: Set up
uses: actions/setup-go@v4
with:
go-version: stable
- name: Build
run: GOOS=freebsd go test -c ./...
- name: Test
uses: cross-platform-actions/action@v0.19.1
with:
operating_system: freebsd
version: '13.2'
run: find . -name '*.test' -maxdepth 1 -exec {} -test.v \;

22
.github/workflows/cross.sh vendored Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
echo android ; GOOS=android GOARCH=amd64 go build .
echo darwin ; GOOS=darwin GOARCH=amd64 go build .
echo dragonfly ; GOOS=dragonfly GOARCH=amd64 go build .
echo freebsd ; GOOS=freebsd GOARCH=amd64 go build .
echo illumos ; GOOS=illumos GOARCH=amd64 go build .
echo ios ; GOOS=ios GOARCH=amd64 go build .
echo linux ; GOOS=linux GOARCH=amd64 go build .
echo netbsd ; GOOS=netbsd GOARCH=amd64 go build .
echo openbsd ; GOOS=openbsd GOARCH=amd64 go build .
echo plan9 ; GOOS=plan9 GOARCH=amd64 go build .
echo solaris ; GOOS=solaris GOARCH=amd64 go build .
echo windows ; GOOS=windows GOARCH=amd64 go build .
# echo aix ; GOOS=aix GOARCH=ppc64 go build .
echo js ; GOOS=js GOARCH=wasm go build .
echo wasip1 ; GOOS=wasip1 GOARCH=wasm go build .
echo darwin-flock ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_flock .
echo darwin-nosys ; GOOS=darwin GOARCH=amd64 go build -tags sqlite3_nosys .
echo linux-nosys ; GOOS=linux GOARCH=amd64 go build -tags sqlite3_nosys .
echo windows-nosys ; GOOS=windows GOARCH=amd64 go build -tags sqlite3_nosys .
echo freebsd-nosys ; GOOS=freebsd GOARCH=amd64 go build -tags sqlite3_nosys .

21
.github/workflows/cross.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Cross compile
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
lfs: 'true'
- name: Set up
uses: actions/setup-go@v4
with:
go-version: stable
- name: Build
run: .github/workflows/cross.sh

View File

@@ -46,27 +46,13 @@ jobs:
- name: Test
run: go test -v ./...
- name: Test no locks
run: go test -v -tags sqlite3_nosys ./tests -run TestDB_nolock
- name: Test BSD locks
run: go test -v -tags sqlite3_flock ./...
if: matrix.os == 'macos-latest'
- name: Build FreeBSD tests
run: GOOS=freebsd go test -c ./...
if: matrix.os == 'macos-latest'
- name: Run FreeBSD tests in a xHyve VM
uses: cross-platform-actions/action@master
with:
operating_system: freebsd
version: '13.2'
run: |
find . -name '*.test' -maxdepth 1 -exec {} -test.v \;
if: matrix.os == 'macos-latest'
- name: Test no locks
run: go test -v -tags sqlite3_nolock .
if: matrix.os == 'ubuntu-latest'
- name: Coverage report
uses: ncruces/go-coverage-report@v0
with:
@@ -76,4 +62,3 @@ jobs:
if: |
github.event_name == 'push' &&
matrix.os == 'ubuntu-latest'
continue-on-error: true

View File

@@ -47,35 +47,31 @@ to use the [`database/sql`](https://pkg.go.dev/database/sql)
driver with WAL mode databases you should disable connection pooling by calling
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
#### POSIX Advisory Locks
#### File Locking
POSIX advisory locks, which SQLite uses, are
POSIX advisory locks, which SQLite uses on Unix, are
[broken by design](https://www.sqlite.org/src/artifact/2e8b12?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.
OFD locks are fully compatible with POSIX advisory locks.
On BSD Unixes, this module uses
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
On BSD Unixes, BSD locks _should_ be compatible with process-associated POSIX advisory locks.
On BSD Unixes, BSD locks are fully compatible with POSIX advisory locks.
##### TL;DR
On Windows, this module uses `LockFile`, `LockFileEx`, and `UnlockFile`, like SQLite.
In all platforms for which this package builds out of the box,
it should be safe to use it to access databases concurrently,
from multiple goroutines, processes, and
with _other_ implementations of SQLite.
If the package does not build for your platform,
see [this](vfs/README.md#portability).
On all other platforms, file locking is not supported, and you must use
[`nolock=1`](https://www.sqlite.org/uri.html#urinolock)
to open database files.
#### Testing
The pure Go VFS is tested by running SQLite's
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c)
on Linux, macOS and Windows.
on Linux, macOS, Windows and FreeBSD.
Performance is tested by running
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).

View File

@@ -25,6 +25,13 @@ func TestDB_file(t *testing.T) {
testDB(t, filepath.Join(t.TempDir(), "test.db"))
}
func TestDB_nolock(t *testing.T) {
t.Parallel()
testDB(t, "file:"+
filepath.ToSlash(filepath.Join(t.TempDir(), "test.db"))+
"?nolock=1")
}
func TestDB_wal(t *testing.T) {
t.Parallel()
wal := filepath.Join(t.TempDir(), "test.db")

View File

@@ -4,27 +4,4 @@ This package implements the SQLite [OS Interface](https://www.sqlite.org/vfs.htm
It replaces the default SQLite VFS with a pure Go implementation.
It also exposes interfaces that should allow you to implement your own custom VFSes.
## Portability
This package is continuously tested on Linux, macOS and Windows,
but it should also work on BSD Unixes and illumos
(code paths for those plaforms are tested on macOS and Linux, respectively).
In all platforms for which this package builds out of the box,
it should be safe to use it to access databases concurrently,
from multiple goroutines, processes, and
with _other_ implementations of SQLite.
If the package does not build for your platform,
you may try to use the `sqlite3_flock` or `sqlite3_nolock` build tags.
The `sqlite3_flock` tag uses
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
It should be safe to access databases concurrently from multiple goroutines and processes,
but **not** with _other_ implementations of SQLite
(_unless_ these are _also_ configured to use `flock`).
The `sqlite3_nolock` tag uses no locking at all.
Database corruption is the likely result from concurrent write access.
It also exposes interfaces that should allow you to implement your own custom VFSes.

View File

@@ -1,12 +1,6 @@
//go:build !sqlite3_nolock
package vfs
import (
"os"
"github.com/ncruces/go-sqlite3/internal/util"
)
import "github.com/ncruces/go-sqlite3/internal/util"
const (
_PENDING_BYTE = 0x40000000
@@ -134,18 +128,3 @@ func (f *vfsFile) CheckReservedLock() (bool, error) {
}
return osCheckReservedLock(f.File)
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetPendingLock(file *os.File) _ErrorCode {
// Acquire the PENDING lock.
return osWriteLock(file, _PENDING_BYTE, 1, 0)
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
return osCheckLock(file, _RESERVED_BYTE, 1)
}

View File

@@ -1,22 +0,0 @@
//go:build sqlite3_nolock
package vfs
const (
_PENDING_BYTE = 0x40000000
_RESERVED_BYTE = (_PENDING_BYTE + 1)
_SHARED_FIRST = (_PENDING_BYTE + 2)
_SHARED_SIZE = 510
)
func (f *vfsFile) Lock(lock LockLevel) error {
return nil
}
func (f *vfsFile) Unlock(lock LockLevel) error {
return nil
}
func (f *vfsFile) CheckReservedLock() (bool, error) {
return false, nil
}

View File

@@ -1,4 +1,4 @@
//go:build (freebsd || openbsd || netbsd || dragonfly || sqlite3_flock) && !sqlite3_nolock
//go:build (freebsd || openbsd || netbsd || dragonfly || sqlite3_flock) && !sqlite3_nosys
package vfs

View File

@@ -1,4 +1,4 @@
//go:build !sqlite3_flock
//go:build !sqlite3_flock && !sqlite3_nosys
package vfs

View File

@@ -1,3 +1,5 @@
//go:build !sqlite3_nosys
package vfs
import (

View File

@@ -1,28 +1,33 @@
//go:build sqlite3_nolock && unix && !darwin
//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) || sqlite3_nosys
package vfs
import (
"os"
"time"
)
import "os"
func osUnlock(file *os.File, start, len int64) _ErrorCode {
return _OK
func osGetSharedLock(file *os.File) _ErrorCode {
return _IOERR_RDLOCK
}
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
return _OK
func osGetReservedLock(file *os.File) _ErrorCode {
return _IOERR_LOCK
}
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return _OK
func osGetPendingLock(file *os.File) _ErrorCode {
return _IOERR_LOCK
}
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return _OK
func osGetExclusiveLock(file *os.File) _ErrorCode {
return _IOERR_LOCK
}
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
return false, _OK
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
return _IOERR_RDLOCK
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
return _IOERR_UNLOCK
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
return false, _IOERR_CHECKRESERVEDLOCK
}

View File

@@ -1,4 +1,4 @@
//go:build (linux || illumos) && !(sqlite3_flock || sqlite3_nolock)
//go:build (linux || illumos) && !sqlite3_nosys
package vfs

View File

@@ -1,4 +1,4 @@
//go:build !unix
//go:build !unix || sqlite3_nosys
package vfs

View File

@@ -1,4 +1,4 @@
//go:build !linux && (!darwin || sqlite3_flock)
//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys
package vfs

View File

@@ -1,4 +1,4 @@
//go:build !unix
//go:build !unix || sqlite3_nosys
package vfs

View File

@@ -1,4 +1,4 @@
//go:build !windows
//go:build !windows || sqlite3_nosys
package vfs

View File

@@ -1,4 +1,4 @@
//go:build !linux && (!darwin || sqlite3_flock)
//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys
package vfs

View File

@@ -1,11 +1,10 @@
//go:build unix
//go:build unix && !sqlite3_nosys
package vfs
import (
"os"
"syscall"
"time"
"golang.org/x/sys/unix"
)
@@ -32,60 +31,3 @@ func osSetMode(file *os.File, modeof string) error {
}
return nil
}
func osGetSharedLock(file *os.File) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if pending, _ := osCheckLock(file, _PENDING_BYTE, 1); pending {
return _BUSY
}
// Acquire the SHARED lock.
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
func osGetExclusiveLock(file *os.File) _ErrorCode {
// Acquire the EXCLUSIVE lock.
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
}
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
// Downgrade to a SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// In theory, the downgrade to a SHARED cannot fail because another
// process is holding an incompatible lock. If it does, this
// indicates that the other process is not following the locking
// protocol. If this happens, return _IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer.
return _IOERR_RDLOCK
}
}
// Release the PENDING and RESERVED locks.
return osUnlock(file, _PENDING_BYTE, 2)
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
// Release all locks.
return osUnlock(file, 0, 0)
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
if errno, ok := err.(unix.Errno); ok {
switch errno {
case
unix.EACCES,
unix.EAGAIN,
unix.EBUSY,
unix.EINTR,
unix.ENOLCK,
unix.EDEADLK,
unix.ETIMEDOUT:
return _BUSY
case unix.EPERM:
return _PERM
}
}
return def
}

82
vfs/os_unix2.go Normal file
View File

@@ -0,0 +1,82 @@
//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos) && !sqlite3_nosys
package vfs
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func osGetSharedLock(file *os.File) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if pending, _ := osCheckLock(file, _PENDING_BYTE, 1); pending {
return _BUSY
}
// Acquire the SHARED lock.
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetPendingLock(file *os.File) _ErrorCode {
// Acquire the PENDING lock.
return osWriteLock(file, _PENDING_BYTE, 1, 0)
}
func osGetExclusiveLock(file *os.File) _ErrorCode {
// Acquire the EXCLUSIVE lock.
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond)
}
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
// Downgrade to a SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// In theory, the downgrade to a SHARED cannot fail because another
// process is holding an incompatible lock. If it does, this
// indicates that the other process is not following the locking
// protocol. If this happens, return _IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer.
return _IOERR_RDLOCK
}
}
// Release the PENDING and RESERVED locks.
return osUnlock(file, _PENDING_BYTE, 2)
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
// Release all locks.
return osUnlock(file, 0, 0)
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
return osCheckLock(file, _RESERVED_BYTE, 1)
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
if errno, ok := err.(unix.Errno); ok {
switch errno {
case
unix.EACCES,
unix.EAGAIN,
unix.EBUSY,
unix.EINTR,
unix.ENOLCK,
unix.EDEADLK,
unix.ETIMEDOUT:
return _BUSY
case unix.EPERM:
return _PERM
}
}
return def
}

View File

@@ -1,3 +1,5 @@
//go:build !sqlite3_nosys
package vfs
import (
@@ -39,6 +41,16 @@ func osGetSharedLock(file *os.File) _ErrorCode {
return rc
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetPendingLock(file *os.File) _ErrorCode {
// Acquire the PENDING lock.
return osWriteLock(file, _PENDING_BYTE, 1, 0)
}
func osGetExclusiveLock(file *os.File) _ErrorCode {
// Release the SHARED lock.
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
@@ -90,6 +102,11 @@ func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
return _OK
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
return osCheckLock(file, _RESERVED_BYTE, 1)
}
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
0, len, 0, &windows.Overlapped{Offset: start})