Windows shared memory. (#181)

This commit is contained in:
Nuno Cruces
2024-11-05 17:30:10 +00:00
committed by GitHub
parent 81e7a94ca4
commit a57ce87157
12 changed files with 380 additions and 95 deletions

View File

@@ -1,4 +1,4 @@
//go:build ((darwin || linux || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk
//go:build ((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk
package vfs
@@ -22,8 +22,5 @@ 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,
}
return &vfsShm{path: path}
}

View File

@@ -18,11 +18,9 @@ type vfsShmFile struct {
*os.File
info os.FileInfo
// +checklocks:vfsShmFilesMtx
refs int
refs int // +checklocks:vfsShmFilesMtx
// +checklocks:Mutex
lock [_SHM_NLOCK]int16
lock [_SHM_NLOCK]int16 // +checklocks:Mutex
sync.Mutex
}
@@ -34,10 +32,9 @@ var (
type vfsShm struct {
*vfsShmFile
path string
lock [_SHM_NLOCK]bool
regions []*util.MappedRegion
readOnly bool
path string
lock [_SHM_NLOCK]bool
regions []*util.MappedRegion
}
func (s *vfsShm) Close() error {
@@ -69,7 +66,7 @@ func (s *vfsShm) Close() error {
panic(util.AssertErr())
}
func (s *vfsShm) shmOpen() (rc _ErrorCode) {
func (s *vfsShm) shmOpen() _ErrorCode {
if s.vfsShmFile != nil {
return _OK
}
@@ -100,17 +97,13 @@ 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.
if s.readOnly {
rc = _READONLY_CANTINIT
} else {
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK {
return rc
}
if err := f.Truncate(0); err != nil {
return _IOERR_SHMOPEN
}
if rc := osLock(f, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK); rc != _OK {
return rc
}
if err := f.Truncate(0); err != nil {
return _IOERR_SHMOPEN
}
// Add the new shared file.
@@ -122,11 +115,11 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) {
for i, g := range vfsShmFiles {
if g == nil {
vfsShmFiles[i] = s.vfsShmFile
return rc
return _OK
}
}
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) {
@@ -148,25 +141,16 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
if !extend {
return 0, _OK
}
if s.readOnly || osAllocate(s.File, n) != nil {
if osAllocate(s.File, n) != 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)
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, false)
if err != nil {
return 0, _IOERR_SHMMAP
}
s.regions = append(s.regions, r)
if s.readOnly {
return r.Ptr, _READONLY
}
return r.Ptr, _OK
}

View File

@@ -4,6 +4,9 @@ package vfs
import (
"context"
"errors"
"io/fs"
"os"
"sync"
"unsafe"
@@ -11,8 +14,6 @@ import (
"github.com/tetratelabs/wazero/api"
)
const _WALINDEX_PGSZ = 32768
type vfsShmBuffer struct {
shared []byte // +checklocks:Mutex
refs int // +checklocks:vfsShmBuffersMtx
@@ -29,15 +30,14 @@ var (
type vfsShm struct {
*vfsShmBuffer
mod api.Module
alloc api.Function
free api.Function
path string
shadow []byte
ptrs []uint32
stack [1]uint64
lock [_SHM_NLOCK]bool
readOnly bool
mod api.Module
alloc api.Function
free api.Function
path string
shadow []byte
ptrs []uint32
stack [1]uint64
lock [_SHM_NLOCK]bool
}
func (s *vfsShm) Close() error {
@@ -58,13 +58,18 @@ func (s *vfsShm) Close() error {
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() {
func (s *vfsShm) shmOpen() _ErrorCode {
if s.vfsShmBuffer != nil {
return
return _OK
}
vfsShmBuffersMtx.Lock()
@@ -74,12 +79,23 @@ func (s *vfsShm) shmOpen() {
if g, ok := vfsShmBuffers[s.path]; ok {
s.vfsShmBuffer = g
g.refs++
return
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) {
@@ -91,8 +107,10 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
s.free = mod.ExportedFunction("sqlite3_free")
s.alloc = mod.ExportedFunction("sqlite3_malloc64")
}
if rc := s.shmOpen(); rc != _OK {
return 0, rc
}
s.shmOpen()
s.Lock()
defer s.Unlock()
defer s.shmAcquire()
@@ -131,7 +149,7 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode {
switch {
case flags&_SHM_LOCK != 0:
s.shmAcquire()
defer s.shmAcquire()
case flags&_SHM_EXCLUSIVE != 0:
s.shmRelease()
}
@@ -228,6 +246,8 @@ func (s *vfsShm) shmBarrier() {
//
// https://sqlite.org/walformat.html#the_wal_index_file_format
const _WALINDEX_PGSZ = 32768
// +checklocks:s.Mutex
func (s *vfsShm) shmAcquire() {
// Copies modified words from shared to private memory.

View File

@@ -1,4 +1,4 @@
//go:build (darwin || linux) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_flock || sqlite3_dotlk || sqlite3_nosys)
//go:build (linux || darwin) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_flock || sqlite3_dotlk || sqlite3_nosys)
package vfs
@@ -21,21 +21,20 @@ type vfsShm struct {
regions []*util.MappedRegion
readOnly bool
blocking bool
barrier sync.Mutex
sync.Mutex
}
var _ blockingSharedMemory = &vfsShm{}
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)
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
}
@@ -65,10 +64,7 @@ func (s *vfsShm) shmOpen() _ErrorCode {
return _IOERR_SHMOPEN
}
}
if rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond); rc != _OK {
return rc
}
return _OK
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) {
@@ -95,13 +91,7 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext
}
}
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)
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, s.readOnly)
if err != nil {
return 0, _IOERR_SHMMAP
}
@@ -157,8 +147,7 @@ func (s *vfsShm) shmUnmap(delete bool) {
for _, r := range s.regions {
r.Unmap()
}
clear(s.regions)
s.regions = s.regions[:0]
s.regions = nil
// Close the file.
if delete {
@@ -169,9 +158,9 @@ func (s *vfsShm) shmUnmap(delete bool) {
}
func (s *vfsShm) shmBarrier() {
s.barrier.Lock()
s.Lock()
//lint:ignore SA2001 memory barrier.
s.barrier.Unlock()
s.Unlock()
}
func (s *vfsShm) shmEnableBlocking(block bool) {

View File

@@ -1,4 +1,4 @@
//go:build !(((darwin || linux || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk)
//go:build !(((linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos) && (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !sqlite3_nosys) || sqlite3_flock || sqlite3_dotlk)
package vfs

242
vfs/shm_windows.go Normal file
View File

@@ -0,0 +1,242 @@
//go:build (386 || arm || amd64 || arm64 || riscv64 || ppc64le) && !(sqlite3_dotlk || sqlite3_nosys)
package vfs
import (
"context"
"io"
"os"
"sync"
"time"
"unsafe"
"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 []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
}
}
// Map the region into memory.
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)
if int(id) >= len(s.shared) {
s.shared = append(s.shared, make([][]byte, int(id)-len(s.shared)+1)...)
}
s.shared[id] = r.Data
// Allocate shadow memory.
if n := (int(id) + 1) * int(size); n > len(s.shadow) {
s.shadow = append(s.shadow, make([]byte, n-len(s.shadow))...)
}
// 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) shmBarrier() {
s.Lock()
s.shmAcquire()
s.shmRelease()
s.Unlock()
}
const _WALINDEX_PGSZ = 32768
func (s *vfsShm) shmAcquire() {
// Copies modified words from shared to private memory.
for id, p := range s.ptrs {
i0 := id * _WALINDEX_PGSZ
i1 := i0 + _WALINDEX_PGSZ
shared := shmPage(s.shared[id])
shadow := shmPage(s.shadow[i0:i1])
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
if shmPageEq(shadow, shared) {
continue
}
for i, shared := range shared {
if shadow[i] != shared {
shadow[i] = shared
privat[i] = shared
}
}
}
}
func (s *vfsShm) shmRelease() {
// Copies modified words from private to shared memory.
for id, p := range s.ptrs {
i0 := id * _WALINDEX_PGSZ
i1 := i0 + _WALINDEX_PGSZ
shared := shmPage(s.shared[id])
shadow := shmPage(s.shadow[i0:i1])
privat := shmPage(util.View(s.mod, p, _WALINDEX_PGSZ))
if shmPageEq(shadow, privat) {
continue
}
for i, privat := range privat {
if shadow[i] != privat {
shadow[i] = privat
shared[i] = privat
}
}
}
}
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 shmPageEq(p1, p2 *[_WALINDEX_PGSZ / 4]uint32) bool {
return *(*[_WALINDEX_PGSZ / 8]uint32)(p1[:]) == *(*[_WALINDEX_PGSZ / 8]uint32)(p2[:])
}
func (s *vfsShm) shmEnableBlocking(block bool) {
s.blocking = block
}

View File

@@ -145,9 +145,6 @@ func Test_crash01(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if os.Getenv("CI") != "" {
t.Skip("skipping in CI")
}
if !vfs.SupportsFileLocking {
t.Skip("skipping without locks")
}
@@ -212,9 +209,6 @@ func Test_crash01_wal(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if os.Getenv("CI") != "" {
t.Skip("skipping in CI")
}
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}