mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
Concurrent testing.
This commit is contained in:
135
.github/coverage.html
vendored
135
.github/coverage.html
vendored
@@ -67,13 +67,13 @@
|
||||
|
||||
<option value="file5">github.com/ncruces/go-sqlite3/stmt.go (27.3%)</option>
|
||||
|
||||
<option value="file6">github.com/ncruces/go-sqlite3/vfs.go (82.5%)</option>
|
||||
<option value="file6">github.com/ncruces/go-sqlite3/vfs.go (83.1%)</option>
|
||||
|
||||
<option value="file7">github.com/ncruces/go-sqlite3/vfs_files.go (80.0%)</option>
|
||||
<option value="file7">github.com/ncruces/go-sqlite3/vfs_files.go (100.0%)</option>
|
||||
|
||||
<option value="file8">github.com/ncruces/go-sqlite3/vfs_lock.go (53.3%)</option>
|
||||
<option value="file8">github.com/ncruces/go-sqlite3/vfs_lock.go (78.3%)</option>
|
||||
|
||||
<option value="file9">github.com/ncruces/go-sqlite3/vfs_unix.go (53.6%)</option>
|
||||
<option value="file9">github.com/ncruces/go-sqlite3/vfs_unix.go (52.5%)</option>
|
||||
|
||||
</select>
|
||||
</div>
|
||||
@@ -784,6 +784,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -907,7 +908,7 @@ func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32)
|
||||
<span class="cov8" title="1">if err != nil </span><span class="cov0" title="0">{
|
||||
return uint32(IOERR_DELETE)
|
||||
}</span>
|
||||
<span class="cov8" title="1">if syncDir != 0 </span><span class="cov8" title="1">{
|
||||
<span class="cov8" title="1">if runtime.GOOS != "windows" && syncDir != 0 </span><span class="cov8" title="1">{
|
||||
f, err := os.Open(filepath.Dir(path))
|
||||
if err == nil </span><span class="cov8" title="1">{
|
||||
err = f.Sync()
|
||||
@@ -1025,7 +1026,7 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfs
|
||||
|
||||
file := vfsFilePtr{mod, pFile}.OSFile()
|
||||
n, err := file.ReadAt(buf, int64(iOfst))
|
||||
if n == int(iAmt) </span><span class="cov0" title="0">{
|
||||
if n == int(iAmt) </span><span class="cov8" title="1">{
|
||||
return _OK
|
||||
}</span>
|
||||
<span class="cov8" title="1">if n == 0 && err != io.EOF </span><span class="cov0" title="0">{
|
||||
@@ -1110,10 +1111,10 @@ func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 <span class="cov8"
|
||||
|
||||
// Reuse an already opened file.
|
||||
for id, of := range vfsOpenFiles </span><span class="cov8" title="1">{
|
||||
if of == nil </span><span class="cov0" title="0">{
|
||||
if of == nil </span><span class="cov8" title="1">{
|
||||
continue</span>
|
||||
}
|
||||
<span class="cov8" title="1">if os.SameFile(info, of.info) </span><span class="cov0" title="0">{
|
||||
<span class="cov8" title="1">if os.SameFile(info, of.info) </span><span class="cov8" title="1">{
|
||||
of.nref++
|
||||
_ = file.Close()
|
||||
return uint32(id)
|
||||
@@ -1130,7 +1131,7 @@ func vfsGetOpenFileID(file *os.File, info os.FileInfo) uint32 <span class="cov8"
|
||||
|
||||
// Find an empty slot.
|
||||
for id, ptr := range vfsOpenFiles </span><span class="cov8" title="1">{
|
||||
if ptr == nil </span><span class="cov0" title="0">{
|
||||
if ptr == nil </span><span class="cov8" title="1">{
|
||||
vfsOpenFiles[id] = of
|
||||
return uint32(id)
|
||||
}</span>
|
||||
@@ -1147,7 +1148,7 @@ func vfsReleaseOpenFile(id uint32) error <span class="cov8" title="1">{
|
||||
defer vfsOpenFilesMtx.Unlock()
|
||||
|
||||
of := vfsOpenFiles[id]
|
||||
if of.nref--; of.nref > 0 </span><span class="cov0" title="0">{
|
||||
if of.nref--; of.nref > 0 </span><span class="cov8" title="1">{
|
||||
return nil
|
||||
}</span>
|
||||
<span class="cov8" title="1">err := of.file.Close()
|
||||
@@ -1294,10 +1295,10 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
|
||||
fLock := of.LockState()
|
||||
|
||||
// If some other connection has a lock that precludes the requested lock, return BUSY.
|
||||
if cLock != fLock && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) </span><span class="cov0" title="0">{
|
||||
if cLock != fLock && (eLock > _SHARED_LOCK || fLock >= _PENDING_LOCK) </span><span class="cov8" title="1">{
|
||||
return uint32(BUSY)
|
||||
}</span>
|
||||
<span class="cov8" title="1">if eLock == _EXCLUSIVE_LOCK && of.shared > 1 </span><span class="cov0" title="0">{
|
||||
<span class="cov8" title="1">if eLock == _EXCLUSIVE_LOCK && of.shared > 1 </span><span class="cov8" title="1">{
|
||||
// We are trying for an exclusive lock but another connection in this
|
||||
// same process is still holding a shared lock.
|
||||
return uint32(BUSY)
|
||||
@@ -1305,11 +1306,11 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
|
||||
|
||||
// If a SHARED lock is requested, and some other connection has a SHARED or RESERVED lock,
|
||||
// then increment the reference count and return OK.
|
||||
<span class="cov8" title="1">if eLock == _SHARED_LOCK && (fLock == _SHARED_LOCK || fLock == _RESERVED_LOCK) </span><span class="cov0" title="0">{
|
||||
<span class="cov8" title="1">if eLock == _SHARED_LOCK && (fLock == _SHARED_LOCK || fLock == _RESERVED_LOCK) </span><span class="cov8" title="1">{
|
||||
if assert && !(cLock == _NO_LOCK && of.shared > 0) </span><span class="cov0" title="0">{
|
||||
panic(assertErr + " [k7coz6]")</span>
|
||||
}
|
||||
<span class="cov0" title="0">ptr.SetLock(_SHARED_LOCK)
|
||||
<span class="cov8" title="1">ptr.SetLock(_SHARED_LOCK)
|
||||
of.shared++
|
||||
return _OK</span>
|
||||
}
|
||||
@@ -1363,7 +1364,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
cLock := ptr.Lock()
|
||||
|
||||
// If we don't have a more restrictive lock, do nothing.
|
||||
if cLock <= eLock </span><span class="cov0" title="0">{
|
||||
if cLock <= eLock </span><span class="cov8" title="1">{
|
||||
return _OK
|
||||
}</span>
|
||||
|
||||
@@ -1381,12 +1382,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
}
|
||||
<span class="cov8" title="1">if eLock == _SHARED_LOCK </span><span class="cov8" title="1">{
|
||||
if rc := of.DowngradeLock(); rc != _OK </span><span class="cov0" title="0">{
|
||||
// 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 uint32(IOERR_RDLOCK)
|
||||
return uint32(rc)
|
||||
}</span>
|
||||
<span class="cov8" title="1">ptr.SetLock(_SHARED_LOCK)
|
||||
return _OK</span>
|
||||
@@ -1399,14 +1395,14 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
// Decrement the shared lock counter. Release the file lock
|
||||
// only when all connections have released the lock.
|
||||
<span class="cov8" title="1">switch </span>{
|
||||
case of.shared > 1:<span class="cov0" title="0">
|
||||
case of.shared > 1:<span class="cov8" title="1">
|
||||
ptr.SetLock(_NO_LOCK)
|
||||
of.shared--
|
||||
return _OK</span>
|
||||
|
||||
case of.shared == 1:<span class="cov8" title="1">
|
||||
if rc := of.Unlock(); rc != _OK </span><span class="cov0" title="0">{
|
||||
return uint32(IOERR_UNLOCK)
|
||||
return uint32(rc)
|
||||
}</span>
|
||||
<span class="cov8" title="1">ptr.SetLock(_NO_LOCK)
|
||||
of.shared = 0
|
||||
@@ -1417,7 +1413,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
}
|
||||
}
|
||||
|
||||
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 <span class="cov0" title="0">{
|
||||
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) uint32 <span class="cov8" title="1">{
|
||||
ptr := vfsFilePtr{mod, pFile}
|
||||
cLock := ptr.Lock()
|
||||
|
||||
@@ -1425,7 +1421,7 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
|
||||
panic(assertErr + " [zarygt]")</span>
|
||||
}
|
||||
|
||||
<span class="cov0" title="0">vfsOpenFilesMtx.Lock()
|
||||
<span class="cov8" title="1">vfsOpenFilesMtx.Lock()
|
||||
defer vfsOpenFilesMtx.Unlock()
|
||||
of := vfsOpenFiles[ptr.ID()]
|
||||
|
||||
@@ -1434,11 +1430,11 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
|
||||
return uint32(IOERR_CHECKRESERVEDLOCK)
|
||||
}</span>
|
||||
|
||||
<span class="cov0" title="0">var res uint32
|
||||
if locked </span><span class="cov0" title="0">{
|
||||
<span class="cov8" title="1">var res uint32
|
||||
if locked </span><span class="cov8" title="1">{
|
||||
res = 1
|
||||
}</span>
|
||||
<span class="cov0" title="0">memory{mod}.writeUint32(pResOut, res)
|
||||
<span class="cov8" title="1">memory{mod}.writeUint32(pResOut, res)
|
||||
return _OK</span>
|
||||
}
|
||||
</pre>
|
||||
@@ -1467,30 +1463,30 @@ func (l *vfsFileLocker) LockShared() xErrorCode <span class="cov8" title="1">{
|
||||
}
|
||||
|
||||
// A PENDING lock is needed before acquiring a SHARED lock.
|
||||
<span class="cov8" title="1">if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
<span class="cov8" title="1">if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 1,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
return IOERR_LOCK
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}</span>
|
||||
|
||||
// Acquire the SHARED lock.
|
||||
<span class="cov8" title="1">if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
<span class="cov8" title="1">if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _SHARED_FIRST,
|
||||
Len: _SHARED_SIZE,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
return IOERR_LOCK
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}</span>
|
||||
<span class="cov8" title="1">l.state = _SHARED_LOCK
|
||||
|
||||
// Relese the PENDING lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 1,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return IOERR_UNLOCK
|
||||
}</span>
|
||||
|
||||
@@ -1503,12 +1499,12 @@ func (l *vfsFileLocker) LockReserved() xErrorCode <span class="cov8" title="1">{
|
||||
}
|
||||
|
||||
// Acquire the RESERVED lock.
|
||||
<span class="cov8" title="1">if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
<span class="cov8" title="1">if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
Start: _RESERVED_BYTE,
|
||||
Len: 1,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
return IOERR_LOCK
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}</span>
|
||||
<span class="cov8" title="1">l.state = _RESERVED_LOCK
|
||||
return _OK</span>
|
||||
@@ -1520,12 +1516,12 @@ func (l *vfsFileLocker) LockPending() xErrorCode <span class="cov8" title="1">{
|
||||
}
|
||||
|
||||
// Acquire the PENDING lock.
|
||||
<span class="cov8" title="1">if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
<span class="cov8" title="1">if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 1,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
return IOERR_LOCK
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}</span>
|
||||
<span class="cov8" title="1">l.state = _PENDING_LOCK
|
||||
return _OK</span>
|
||||
@@ -1537,12 +1533,12 @@ func (l *vfsFileLocker) LockExclusive() xErrorCode <span class="cov8" title="1">
|
||||
}
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
<span class="cov8" title="1">if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
<span class="cov8" title="1">if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
Start: _SHARED_FIRST,
|
||||
Len: _SHARED_SIZE,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
return IOERR_LOCK
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}</span>
|
||||
<span class="cov8" title="1">l.state = _EXCLUSIVE_LOCK
|
||||
return _OK</span>
|
||||
@@ -1554,21 +1550,26 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode <span class="cov8" title="1">
|
||||
}
|
||||
|
||||
// Downgrade to a SHARED lock.
|
||||
<span class="cov8" title="1">if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
<span class="cov8" title="1">if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _SHARED_FIRST,
|
||||
Len: _SHARED_SIZE,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
// 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
|
||||
}</span>
|
||||
<span class="cov8" title="1">l.state = _SHARED_LOCK
|
||||
|
||||
// Release the PENDING and RESERVED locks.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 2,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return IOERR_UNLOCK
|
||||
}</span>
|
||||
<span class="cov8" title="1">return _OK</span>
|
||||
@@ -1576,43 +1577,61 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode <span class="cov8" title="1">
|
||||
|
||||
func (l *vfsFileLocker) Unlock() xErrorCode <span class="cov8" title="1">{
|
||||
// Release all locks.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
}) </span><span class="cov0" title="0">{
|
||||
}); err != nil </span><span class="cov0" title="0">{
|
||||
return IOERR_UNLOCK
|
||||
}</span>
|
||||
<span class="cov8" title="1">l.state = _NO_LOCK
|
||||
return _OK</span>
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) <span class="cov0" title="0">{
|
||||
if l.state >= _RESERVED_LOCK </span><span class="cov0" title="0">{
|
||||
func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) <span class="cov8" title="1">{
|
||||
if l.state >= _RESERVED_LOCK </span><span class="cov8" title="1">{
|
||||
return true, _OK
|
||||
}</span>
|
||||
// Test all write locks.
|
||||
<span class="cov0" title="0">lock := syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
}
|
||||
if !l.fcntlGetLock(&lock) </span><span class="cov0" title="0">{
|
||||
if l.fcntlGetLock(&lock) != nil </span><span class="cov0" title="0">{
|
||||
return false, IOERR_CHECKRESERVEDLOCK
|
||||
}</span>
|
||||
<span class="cov0" title="0">return lock.Type == syscall.F_UNLCK, _OK</span>
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) bool <span class="cov0" title="0">{
|
||||
func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error <span class="cov0" title="0">{
|
||||
F_GETLK := syscall.F_GETLK
|
||||
if runtime.GOOS == "linux" </span><span class="cov0" title="0">{
|
||||
F_GETLK = 36 // F_OFD_GETLK
|
||||
}</span>
|
||||
<span class="cov0" title="0">return syscall.FcntlFlock(l.Fd(), F_GETLK, lock) == nil</span>
|
||||
<span class="cov0" title="0">return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)</span>
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) bool <span class="cov8" title="1">{
|
||||
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error <span class="cov8" title="1">{
|
||||
F_SETLK := syscall.F_SETLK
|
||||
if runtime.GOOS == "linux" </span><span class="cov0" title="0">{
|
||||
F_SETLK = 37 // F_OFD_SETLK
|
||||
}</span>
|
||||
<span class="cov8" title="1">return syscall.FcntlFlock(l.Fd(), F_SETLK, lock) == nil</span>
|
||||
<span class="cov8" title="1">return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)</span>
|
||||
}
|
||||
|
||||
func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode <span class="cov0" title="0">{
|
||||
if errno, ok := err.(syscall.Errno); ok </span><span class="cov0" title="0">{
|
||||
switch errno </span>{
|
||||
case syscall.EACCES:<span class="cov0" title="0"></span>
|
||||
case syscall.EAGAIN:<span class="cov0" title="0"></span>
|
||||
case syscall.EBUSY:<span class="cov0" title="0"></span>
|
||||
case syscall.EINTR:<span class="cov0" title="0"></span>
|
||||
case syscall.ENOLCK:<span class="cov0" title="0"></span>
|
||||
case syscall.EDEADLK:<span class="cov0" title="0"></span>
|
||||
case syscall.ETIMEDOUT:<span class="cov0" title="0">
|
||||
return xErrorCode(BUSY)</span>
|
||||
case syscall.EPERM:<span class="cov0" title="0">
|
||||
return xErrorCode(PERM)</span>
|
||||
}
|
||||
}
|
||||
<span class="cov0" title="0">return def</span>
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
7
.github/coverage.sh
vendored
7
.github/coverage.sh
vendored
@@ -5,15 +5,14 @@ SCRIPT_DIR="$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
|
||||
go test ./... -coverprofile "$SCRIPT_DIR/coverage.out"
|
||||
go tool cover -html="$SCRIPT_DIR/coverage.out" -o "$SCRIPT_DIR/coverage.html"
|
||||
COVERAGE=$(go tool cover -func="$SCRIPT_DIR/coverage.out" | grep total: | grep -Eo '[0-9]+\.[0-9]+')
|
||||
COVERAGE=$(go tool cover -func="$SCRIPT_DIR/coverage.out" | tail -1 | grep -Eo '\d+\.\d')
|
||||
|
||||
echo
|
||||
echo "coverage: $COVERAGE% of statements"
|
||||
|
||||
COLOR=orange
|
||||
if (( $(echo "$COVERAGE <= 50" | bc -l) )) ; then
|
||||
if awk "BEGIN {exit !($COVERAGE <= 50)}"; then
|
||||
COLOR=red
|
||||
elif (( $(echo "$COVERAGE > 80" | bc -l) )); then
|
||||
elif awk "BEGIN {exit !($COVERAGE > 80)}"; then
|
||||
COLOR=green
|
||||
fi
|
||||
curl -s "https://img.shields.io/badge/coverage-$COVERAGE%25-$COLOR" > "$SCRIPT_DIR/coverage.svg"
|
||||
|
||||
2
.github/coverage.svg
vendored
2
.github/coverage.svg
vendored
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20" role="img" aria-label="coverage: 66.6%"><title>coverage: 66.6%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#fe7d37"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">66.6%</text><text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="370">66.6%</text></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20" role="img" aria-label="coverage: 71.4%"><title>coverage: 71.4%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#fe7d37"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">71.4%</text><text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="370">71.4%</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
2
go.mod
2
go.mod
@@ -6,3 +6,5 @@ require (
|
||||
github.com/ncruces/julianday v0.1.3
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.7.0.20230117161130-15889085a5ca
|
||||
)
|
||||
|
||||
require golang.org/x/sync v0.1.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -2,3 +2,5 @@ github.com/ncruces/julianday v0.1.3 h1:+JRqsbdGH8QlPWlB6i/pD8sENBZZ1qg24qBCAcYfG
|
||||
github.com/ncruces/julianday v0.1.3/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.7.0.20230117161130-15889085a5ca h1:cvbRnzLE99QnA6HT3yoqXjWEB2JMiyTs3GSLpQZg45A=
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.7.0.20230117161130-15889085a5ca/go.mod h1:u8wrFmpdrykiFK0DFPiFm5a4+0RzsdmXYVtijBKqUVo=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
159
sqlite3_test.go
159
sqlite3_test.go
@@ -5,43 +5,29 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func TestOpen_memory(t *testing.T) {
|
||||
testOpen(t, ":memory:")
|
||||
func TestDB_memory(t *testing.T) {
|
||||
testDB(t, ":memory:")
|
||||
}
|
||||
|
||||
func TestOpen_file(t *testing.T) {
|
||||
func TestDB_file(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "sqlite3-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
testOpen(t, filepath.Join(dir, "test.db"))
|
||||
testDB(t, filepath.Join(dir, "test.db"))
|
||||
}
|
||||
|
||||
func TestOpen_dir(t *testing.T) {
|
||||
_, err := sqlite3.Open(".")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
var serr *sqlite3.Error
|
||||
if !errors.As(err, &serr) {
|
||||
t.Fatal("want sqlite3.Error")
|
||||
}
|
||||
if serr.Code != sqlite3.CANTOPEN {
|
||||
t.Error("want sqlite3.CANTOPEN")
|
||||
}
|
||||
if got := err.Error(); got != "sqlite3: unable to open database file" {
|
||||
t.Error("got message: ", got)
|
||||
}
|
||||
}
|
||||
|
||||
func testOpen(t *testing.T, name string) {
|
||||
func testDB(t *testing.T, name string) {
|
||||
db, err := sqlite3.Open(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -63,23 +49,22 @@ func testOpen(t *testing.T, name string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
row := 0
|
||||
ids := []int{0, 1, 2}
|
||||
names := []string{"go", "zig", "whatever"}
|
||||
|
||||
idx := 0
|
||||
for ; stmt.Step(); idx++ {
|
||||
if ids[idx] != stmt.ColumnInt(0) {
|
||||
t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[idx])
|
||||
for ; stmt.Step(); row++ {
|
||||
if ids[row] != stmt.ColumnInt(0) {
|
||||
t.Errorf("got %d, want %d", stmt.ColumnInt(0), ids[row])
|
||||
}
|
||||
if names[idx] != stmt.ColumnText(1) {
|
||||
t.Errorf("got %q, want %q", stmt.ColumnText(1), names[idx])
|
||||
if names[row] != stmt.ColumnText(1) {
|
||||
t.Errorf("got %q, want %q", stmt.ColumnText(1), names[row])
|
||||
}
|
||||
}
|
||||
if err := stmt.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if idx != 3 {
|
||||
t.Errorf("got %d rows, want %d", idx, len(ids))
|
||||
if row != 3 {
|
||||
t.Errorf("got %d rows, want %d", row, len(ids))
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
@@ -92,3 +77,115 @@ func testOpen(t *testing.T, name string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDB_parallel(t *testing.T) {
|
||||
dir, err := os.MkdirTemp("", "sqlite3-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
writer := func() error {
|
||||
db, err := sqlite3.Open(filepath.Join(dir, "test.db"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`
|
||||
PRAGMA locking_mode = NORMAL;
|
||||
PRAGMA busy_timeout = 1000;
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO users(id, name) VALUES(0, 'go'), (1, 'zig'), (2, 'whatever')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
reader := func() error {
|
||||
db, err := sqlite3.Open(filepath.Join(dir, "test.db"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`
|
||||
PRAGMA locking_mode = NORMAL;
|
||||
PRAGMA busy_timeout = 1000;
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
row := 0
|
||||
for stmt.Step() {
|
||||
row++
|
||||
}
|
||||
if err := stmt.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row%3 != 0 {
|
||||
t.Errorf("got %d rows, want multiple of 3", row)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
err = writer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var group errgroup.Group
|
||||
group.SetLimit(4)
|
||||
for i := 0; i < 32; i++ {
|
||||
if i&7 != 7 {
|
||||
group.Go(reader)
|
||||
} else {
|
||||
group.Go(writer)
|
||||
}
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
err = group.Wait()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpen_dir(t *testing.T) {
|
||||
_, err := sqlite3.Open(".")
|
||||
if err == nil {
|
||||
t.Fatal("want error")
|
||||
}
|
||||
var serr *sqlite3.Error
|
||||
if !errors.As(err, &serr) {
|
||||
t.Fatal("want sqlite3.Error")
|
||||
}
|
||||
if serr.Code != sqlite3.CANTOPEN {
|
||||
t.Error("want sqlite3.CANTOPEN")
|
||||
}
|
||||
if got := err.Error(); got != "sqlite3: unable to open database file" {
|
||||
t.Error("got message: ", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,12 +193,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
}
|
||||
if eLock == _SHARED_LOCK {
|
||||
if rc := of.DowngradeLock(); 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 uint32(IOERR_RDLOCK)
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_SHARED_LOCK)
|
||||
return _OK
|
||||
@@ -218,7 +213,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
|
||||
|
||||
case of.shared == 1:
|
||||
if rc := of.Unlock(); rc != _OK {
|
||||
return uint32(IOERR_UNLOCK)
|
||||
return uint32(rc)
|
||||
}
|
||||
ptr.SetLock(_NO_LOCK)
|
||||
of.shared = 0
|
||||
|
||||
79
vfs_unix.go
79
vfs_unix.go
@@ -22,30 +22,30 @@ func (l *vfsFileLocker) LockShared() xErrorCode {
|
||||
}
|
||||
|
||||
// A PENDING lock is needed before acquiring a SHARED lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 1,
|
||||
}) {
|
||||
return IOERR_LOCK
|
||||
}); err != nil {
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}
|
||||
|
||||
// Acquire the SHARED lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _SHARED_FIRST,
|
||||
Len: _SHARED_SIZE,
|
||||
}) {
|
||||
return IOERR_LOCK
|
||||
}); err != nil {
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}
|
||||
l.state = _SHARED_LOCK
|
||||
|
||||
// Relese the PENDING lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 1,
|
||||
}) {
|
||||
}); err != nil {
|
||||
return IOERR_UNLOCK
|
||||
}
|
||||
|
||||
@@ -58,12 +58,12 @@ func (l *vfsFileLocker) LockReserved() xErrorCode {
|
||||
}
|
||||
|
||||
// Acquire the RESERVED lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
Start: _RESERVED_BYTE,
|
||||
Len: 1,
|
||||
}) {
|
||||
return IOERR_LOCK
|
||||
}); err != nil {
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}
|
||||
l.state = _RESERVED_LOCK
|
||||
return _OK
|
||||
@@ -75,12 +75,12 @@ func (l *vfsFileLocker) LockPending() xErrorCode {
|
||||
}
|
||||
|
||||
// Acquire the PENDING lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 1,
|
||||
}) {
|
||||
return IOERR_LOCK
|
||||
}); err != nil {
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}
|
||||
l.state = _PENDING_LOCK
|
||||
return _OK
|
||||
@@ -92,12 +92,12 @@ func (l *vfsFileLocker) LockExclusive() xErrorCode {
|
||||
}
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_WRLCK,
|
||||
Start: _SHARED_FIRST,
|
||||
Len: _SHARED_SIZE,
|
||||
}) {
|
||||
return IOERR_LOCK
|
||||
}); err != nil {
|
||||
return l.errorCode(err, IOERR_LOCK)
|
||||
}
|
||||
l.state = _EXCLUSIVE_LOCK
|
||||
return _OK
|
||||
@@ -109,21 +109,26 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode {
|
||||
}
|
||||
|
||||
// Downgrade to a SHARED lock.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
Start: _SHARED_FIRST,
|
||||
Len: _SHARED_SIZE,
|
||||
}) {
|
||||
}); err != nil {
|
||||
// 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
|
||||
}
|
||||
l.state = _SHARED_LOCK
|
||||
|
||||
// Release the PENDING and RESERVED locks.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: _PENDING_BYTE,
|
||||
Len: 2,
|
||||
}) {
|
||||
}); err != nil {
|
||||
return IOERR_UNLOCK
|
||||
}
|
||||
return _OK
|
||||
@@ -131,9 +136,9 @@ func (l *vfsFileLocker) DowngradeLock() xErrorCode {
|
||||
|
||||
func (l *vfsFileLocker) Unlock() xErrorCode {
|
||||
// Release all locks.
|
||||
if !l.fcntlSetLock(&syscall.Flock_t{
|
||||
if err := l.fcntlSetLock(&syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
}) {
|
||||
}); err != nil {
|
||||
return IOERR_UNLOCK
|
||||
}
|
||||
l.state = _NO_LOCK
|
||||
@@ -148,24 +153,42 @@ func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) {
|
||||
lock := syscall.Flock_t{
|
||||
Type: syscall.F_RDLCK,
|
||||
}
|
||||
if !l.fcntlGetLock(&lock) {
|
||||
if l.fcntlGetLock(&lock) != nil {
|
||||
return false, IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
return lock.Type == syscall.F_UNLCK, _OK
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) bool {
|
||||
func (l *vfsFileLocker) fcntlGetLock(lock *syscall.Flock_t) error {
|
||||
F_GETLK := syscall.F_GETLK
|
||||
if runtime.GOOS == "linux" {
|
||||
F_GETLK = 36 // F_OFD_GETLK
|
||||
}
|
||||
return syscall.FcntlFlock(l.Fd(), F_GETLK, lock) == nil
|
||||
return syscall.FcntlFlock(l.Fd(), F_GETLK, lock)
|
||||
}
|
||||
|
||||
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) bool {
|
||||
func (l *vfsFileLocker) fcntlSetLock(lock *syscall.Flock_t) error {
|
||||
F_SETLK := syscall.F_SETLK
|
||||
if runtime.GOOS == "linux" {
|
||||
F_SETLK = 37 // F_OFD_SETLK
|
||||
}
|
||||
return syscall.FcntlFlock(l.Fd(), F_SETLK, lock) == nil
|
||||
return syscall.FcntlFlock(l.Fd(), F_SETLK, lock)
|
||||
}
|
||||
|
||||
func (vfsFileLocker) errorCode(err error, def xErrorCode) xErrorCode {
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
switch errno {
|
||||
case syscall.EACCES:
|
||||
case syscall.EAGAIN:
|
||||
case syscall.EBUSY:
|
||||
case syscall.EINTR:
|
||||
case syscall.ENOLCK:
|
||||
case syscall.EDEADLK:
|
||||
case syscall.ETIMEDOUT:
|
||||
return xErrorCode(BUSY)
|
||||
case syscall.EPERM:
|
||||
return xErrorCode(PERM)
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user