From 7f5ea54009725d19ed984e6e83b9b0776b69af4f Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 11 Dec 2024 15:05:22 +0000 Subject: [PATCH] Windows blocking locks. (#200) --- README.md | 2 +- go.work.sum | 1 + util/osutil/open_windows.go | 3 +++ vfs/os_windows.go | 43 +++++++++++++++++++++++-------------- vfs/shm_bsd.go | 2 +- vfs/shm_ofd.go | 12 ++++++++--- vfs/shm_windows.go | 12 +++++++++-- 7 files changed, 52 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f5394ab..d3d5e51 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite, and uses [wazero](https://wazero.io/) as the runtime.\ -Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies. +Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ direct dependencies. ### Getting started diff --git a/go.work.sum b/go.work.sum index c393696..f41ac55 100644 --- a/go.work.sum +++ b/go.work.sum @@ -10,5 +10,6 @@ golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= diff --git a/util/osutil/open_windows.go b/util/osutil/open_windows.go index 417faa5..febaf84 100644 --- a/util/osutil/open_windows.go +++ b/util/osutil/open_windows.go @@ -101,6 +101,9 @@ func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) { const _FILE_FLAG_WRITE_THROUGH = 0x80000000 attrs |= _FILE_FLAG_WRITE_THROUGH } + if mode&O_NONBLOCK != 0 { + attrs |= FILE_FLAG_OVERLAPPED + } return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) } diff --git a/vfs/os_windows.go b/vfs/os_windows.go index 2d4342e..2872201 100644 --- a/vfs/os_windows.go +++ b/vfs/os_windows.go @@ -3,7 +3,6 @@ package vfs import ( - "math/rand" "os" "time" @@ -46,7 +45,7 @@ func osGetExclusiveLock(file *os.File, state *LockLevel) _ErrorCode { osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) // Acquire the EXCLUSIVE lock. - rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, time.Millisecond) + rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) if rc != _OK { // Reacquire the SHARED lock. @@ -123,29 +122,40 @@ func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def var err error switch { case timeout == 0: - err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) + err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len, 0) case timeout < 0: - err = osLockEx(file, flags, start, len) + err = osLockEx(file, flags, start, len, 0) default: - before := time.Now() - for { - err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) - if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION { - break + var event windows.Handle + event, err = windows.CreateEvent(nil, 1, 0, nil) + if err != nil { + break + } + defer windows.CloseHandle(event) + + err = osLockEx(file, flags, start, len, event) + if err == windows.ERROR_IO_PENDING { + rc, serr := windows.WaitForSingleObject(event, uint32(timeout/time.Millisecond)) + if rc == windows.WAIT_OBJECT_0 { + return _OK } - if time.Since(before) > timeout { - break + if serr != nil { + err = serr + } else { + err = windows.Errno(rc) } - const sleepIncrement = 1024*1024 - 1 // power of two, ~1ms - time.Sleep(time.Duration(rand.Int63() & sleepIncrement)) + windows.CancelIo(windows.Handle(file.Fd())) } } return osLockErrorCode(err, def) } -func osLockEx(file *os.File, flags, start, len uint32) error { +func osLockEx(file *os.File, flags, start, len uint32, event windows.Handle) error { return windows.LockFileEx(windows.Handle(file.Fd()), flags, - 0, len, 0, &windows.Overlapped{Offset: start}) + 0, len, 0, &windows.Overlapped{ + Offset: start, + HEvent: event, + }) } func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { @@ -166,7 +176,8 @@ func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { case windows.ERROR_LOCK_VIOLATION, windows.ERROR_IO_PENDING, - windows.ERROR_OPERATION_ABORTED: + windows.ERROR_OPERATION_ABORTED, + windows.WAIT_TIMEOUT: return _BUSY } } diff --git a/vfs/shm_bsd.go b/vfs/shm_bsd.go index e4850ab..8e7f270 100644 --- a/vfs/shm_bsd.go +++ b/vfs/shm_bsd.go @@ -73,7 +73,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { // Always open file read-write, as it will be shared. f, err := os.OpenFile(s.path, - unix.O_RDWR|unix.O_CREAT|unix.O_NOFOLLOW, 0666) + os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666) if err != nil { return _CANTOPEN } diff --git a/vfs/shm_ofd.go b/vfs/shm_ofd.go index 08d0382..d335a85 100644 --- a/vfs/shm_ofd.go +++ b/vfs/shm_ofd.go @@ -20,6 +20,7 @@ type vfsShm struct { path string regions []*util.MappedRegion readOnly bool + fileLock bool blocking bool sync.Mutex } @@ -29,10 +30,10 @@ var _ blockingSharedMemory = &vfsShm{} func (s *vfsShm) shmOpen() _ErrorCode { if s.File == nil { f, err := os.OpenFile(s.path, - unix.O_RDWR|unix.O_CREAT|unix.O_NOFOLLOW, 0666) + os.O_RDWR|os.O_CREATE|_O_NOFOLLOW, 0666) if err != nil { f, err = os.OpenFile(s.path, - unix.O_RDONLY|unix.O_CREAT|unix.O_NOFOLLOW, 0666) + os.O_RDONLY|os.O_CREATE|_O_NOFOLLOW, 0666) s.readOnly = true } if err != nil { @@ -40,6 +41,9 @@ func (s *vfsShm) shmOpen() _ErrorCode { } s.File = f } + if s.fileLock { + return _OK + } // Dead man's switch. if lock, rc := osTestLock(s.File, _SHM_DMS, 1); rc != _OK { @@ -64,7 +68,9 @@ func (s *vfsShm) shmOpen() _ErrorCode { return _IOERR_SHMOPEN } } - return osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) + rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) + s.fileLock = rc == _OK + return rc } func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, _ErrorCode) { diff --git a/vfs/shm_windows.go b/vfs/shm_windows.go index 4c9bad5..1de5764 100644 --- a/vfs/shm_windows.go +++ b/vfs/shm_windows.go @@ -7,6 +7,7 @@ import ( "io" "os" "sync" + "syscall" "time" "github.com/tetratelabs/wazero/api" @@ -27,6 +28,7 @@ type vfsShm struct { shadow [][_WALINDEX_PGSZ]byte ptrs []uint32 stack [1]uint64 + fileLock bool blocking bool sync.Mutex } @@ -46,12 +48,16 @@ func (s *vfsShm) Close() error { func (s *vfsShm) shmOpen() _ErrorCode { if s.File == nil { - f, err := osutil.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666) + f, err := osutil.OpenFile(s.path, + os.O_RDWR|os.O_CREATE|syscall.O_NONBLOCK, 0666) if err != nil { return _CANTOPEN } s.File = f } + if s.fileLock { + return _OK + } // Dead man's switch. if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc == _OK { @@ -61,7 +67,9 @@ func (s *vfsShm) shmOpen() _ErrorCode { return _IOERR_SHMOPEN } } - return osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) + rc := osReadLock(s.File, _SHM_DMS, 1, time.Millisecond) + s.fileLock = rc == _OK + return rc } func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ uint32, rc _ErrorCode) {