working commit
This commit is contained in:
Generated
+24
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint
|
||||
- bidichk
|
||||
- dogsled
|
||||
- dupword
|
||||
- durationcheck
|
||||
- err113
|
||||
- errname
|
||||
- errorlint
|
||||
- fatcontext
|
||||
- forbidigo
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoinits
|
||||
- gocritic
|
||||
- godot
|
||||
- godox
|
||||
- goheader
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- inamedparam
|
||||
- interfacebloat
|
||||
- ireturn
|
||||
- mirror
|
||||
- misspell
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- thelper
|
||||
- unconvert
|
||||
- unparam
|
||||
- usestdlibvars
|
||||
- whitespace
|
||||
- wsl_v5
|
||||
settings:
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- paramTypeCombine # already handle by gofumpt.extra-rules
|
||||
- whyNoLint # already handle by nonolint
|
||||
- unnamedResult
|
||||
- hugeParam
|
||||
- sloppyReassign
|
||||
- rangeValCopy
|
||||
- octalLiteral
|
||||
- ptrToRefParam
|
||||
- appendAssign
|
||||
- ruleguard
|
||||
- httpNoBody
|
||||
- exposedSyncMutex
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- style
|
||||
- performance
|
||||
godox:
|
||||
keywords:
|
||||
- FIXME
|
||||
goheader:
|
||||
template: |-
|
||||
Copyright 2015 Tim Heckman. All rights reserved.
|
||||
Copyright 2018-{{ YEAR }} The Gofrs. All rights reserved.
|
||||
Use of this source code is governed by the BSD 3-Clause
|
||||
license that can be found in the LICENSE file.
|
||||
gosec:
|
||||
excludes:
|
||||
- G115
|
||||
misspell:
|
||||
locale: US
|
||||
revive:
|
||||
rules:
|
||||
- name: struct-tag
|
||||
- name: blank-imports
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
- name: if-return
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
- name: indent-error-flow
|
||||
- name: errorf
|
||||
- name: empty-block
|
||||
- name: superfluous-else
|
||||
- name: unused-parameter
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
exclusions:
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- std-error-handling
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
Copyright (c) 2018-2025, The Gofrs
|
||||
Copyright (c) 2015-2020, Tim Heckman
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of gofrs nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# flock
|
||||
|
||||
[](https://pkg.go.dev/github.com/gofrs/flock)
|
||||
[](https://github.com/gofrs/flock/blob/main/LICENSE)
|
||||
[](https://goreportcard.com/report/github.com/gofrs/flock)
|
||||
|
||||
`flock` implements a thread-safe file lock.
|
||||
|
||||
It also includes a non-blocking `TryLock()` function to allow locking without blocking execution.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get -u github.com/gofrs/flock
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import "github.com/gofrs/flock"
|
||||
|
||||
fileLock := flock.New("/var/lock/go-lock.lock")
|
||||
|
||||
locked, err := fileLock.TryLock()
|
||||
|
||||
if err != nil {
|
||||
// handle locking error
|
||||
}
|
||||
|
||||
if locked {
|
||||
// do work
|
||||
fileLock.Unlock()
|
||||
}
|
||||
```
|
||||
|
||||
For more detailed usage information take a look at the package API docs on
|
||||
[GoDoc](https://pkg.go.dev/github.com/gofrs/flock).
|
||||
|
||||
## License
|
||||
|
||||
`flock` is released under the BSD 3-Clause License. See the [`LICENSE`](./LICENSE) file for more details.
|
||||
|
||||
## Project History
|
||||
|
||||
This project was originally `github.com/theckman/go-flock`, it was transferred to Gofrs by the original author [Tim Heckman ](https://github.com/theckman).
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We support the latest version of this library.
|
||||
We do not guarantee support of previous versions.
|
||||
|
||||
If a defect is reported, it will generally be fixed on the latest version (provided it exists) irrespective of whether it was introduced in a prior version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a potential security vulnerability, please create a [security advisory](https://github.com/gofrs/flock/security/advisories/new).
|
||||
|
||||
For us to respond to your report most effectively, please include any of the following:
|
||||
|
||||
- Steps to reproduce or a proof-of-concept
|
||||
- Any relevant information, including the versions used
|
||||
|
||||
## Security Scorecard
|
||||
|
||||
This project submits security [results](https://scorecard.dev/viewer/?uri=github.com/gofrs/flock) to the [OpenSSF Scorecard](https://securityscorecards.dev/).
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# Not supported by flock:
|
||||
# - plan9/*
|
||||
# - js/wasm
|
||||
# - wasp1/wasm
|
||||
|
||||
for row in $(go tool dist list -json | jq -r '.[] | @base64'); do
|
||||
_jq() {
|
||||
echo ${row} | base64 --decode | jq -r ${1}
|
||||
}
|
||||
|
||||
GOOS=$(_jq '.GOOS')
|
||||
GOARCH=$(_jq '.GOARCH')
|
||||
|
||||
echo "$GOOS/$GOARCH"
|
||||
GOOS=$GOOS GOARCH=$GOARCH go build
|
||||
done
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Copyright 2018-2025 The Gofrs. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package flock implements a thread-safe interface for file locking.
|
||||
// It also includes a non-blocking TryLock() function to allow locking
|
||||
// without blocking execution.
|
||||
//
|
||||
// Package flock is released under the BSD 3-Clause License. See the LICENSE file
|
||||
// for more details.
|
||||
//
|
||||
// While using this library, remember that the locking behaviors are not
|
||||
// guaranteed to be the same on each platform. For example, some UNIX-like
|
||||
// operating systems will transparently convert a shared lock to an exclusive
|
||||
// lock. If you Unlock() the flock from a location where you believe that you
|
||||
// have the shared lock, you may accidentally drop the exclusive lock.
|
||||
package flock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Option func(f *Flock)
|
||||
|
||||
// SetFlag sets the flag used to create/open the file.
|
||||
func SetFlag(flag int) Option {
|
||||
return func(f *Flock) {
|
||||
f.flag = flag
|
||||
}
|
||||
}
|
||||
|
||||
// SetPermissions sets the OS permissions to set on the file.
|
||||
func SetPermissions(perm fs.FileMode) Option {
|
||||
return func(f *Flock) {
|
||||
f.perm = perm
|
||||
}
|
||||
}
|
||||
|
||||
// Flock is the struct type to handle file locking. All fields are unexported,
|
||||
// with access to some of the fields provided by getter methods (Path() and Locked()).
|
||||
type Flock struct {
|
||||
path string
|
||||
m sync.RWMutex
|
||||
fh *os.File
|
||||
l bool
|
||||
r bool
|
||||
|
||||
// flag is the flag used to create/open the file.
|
||||
flag int
|
||||
// perm is the OS permissions to set on the file.
|
||||
perm fs.FileMode
|
||||
}
|
||||
|
||||
// New returns a new instance of *Flock. The only parameter
|
||||
// it takes is the path to the desired lockfile.
|
||||
func New(path string, opts ...Option) *Flock {
|
||||
// create it if it doesn't exist, and open the file read-only.
|
||||
flags := os.O_CREATE
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "aix", "solaris", "illumos":
|
||||
// AIX cannot preform write-lock (i.e. exclusive) on a read-only file.
|
||||
flags |= os.O_RDWR
|
||||
default:
|
||||
flags |= os.O_RDONLY
|
||||
}
|
||||
|
||||
f := &Flock{
|
||||
path: path,
|
||||
flag: flags,
|
||||
perm: fs.FileMode(0o600),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(f)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// NewFlock returns a new instance of *Flock. The only parameter
|
||||
// it takes is the path to the desired lockfile.
|
||||
//
|
||||
// Deprecated: Use New instead.
|
||||
func NewFlock(path string) *Flock {
|
||||
return New(path)
|
||||
}
|
||||
|
||||
// Close is equivalent to calling Unlock.
|
||||
//
|
||||
// This will release the lock and close the underlying file descriptor.
|
||||
// It will not remove the file from disk, that's up to your application.
|
||||
func (f *Flock) Close() error {
|
||||
return f.Unlock()
|
||||
}
|
||||
|
||||
// Path returns the path as provided in NewFlock().
|
||||
func (f *Flock) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// Locked returns the lock state (locked: true, unlocked: false).
|
||||
//
|
||||
// Warning: by the time you use the returned value, the state may have changed.
|
||||
func (f *Flock) Locked() bool {
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
|
||||
return f.l
|
||||
}
|
||||
|
||||
// RLocked returns the read lock state (locked: true, unlocked: false).
|
||||
//
|
||||
// Warning: by the time you use the returned value, the state may have changed.
|
||||
func (f *Flock) RLocked() bool {
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
|
||||
return f.r
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing the lock file.
|
||||
// If the lock file does not exist or cannot be accessed, an error is returned.
|
||||
//
|
||||
// This can be used to check the modification time of the lock file,
|
||||
// which is useful for detecting stale locks.
|
||||
func (f *Flock) Stat() (fs.FileInfo, error) {
|
||||
f.m.RLock()
|
||||
defer f.m.RUnlock()
|
||||
|
||||
if f.fh != nil {
|
||||
return f.fh.Stat()
|
||||
}
|
||||
|
||||
return os.Stat(f.path)
|
||||
}
|
||||
|
||||
func (f *Flock) String() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// TryLockContext repeatedly tries to take an exclusive lock until one of the conditions is met:
|
||||
// - TryLock succeeds
|
||||
// - TryLock fails with error
|
||||
// - Context Done channel is closed.
|
||||
func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
|
||||
return tryCtx(ctx, f.TryLock, retryDelay)
|
||||
}
|
||||
|
||||
// TryRLockContext repeatedly tries to take a shared lock until one of the conditions is met:
|
||||
// - TryRLock succeeds
|
||||
// - TryRLock fails with error
|
||||
// - Context Done channel is closed.
|
||||
func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) {
|
||||
return tryCtx(ctx, f.TryRLock, retryDelay)
|
||||
}
|
||||
|
||||
func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) {
|
||||
if ctx.Err() != nil {
|
||||
return false, ctx.Err()
|
||||
}
|
||||
|
||||
for {
|
||||
if ok, err := fn(); ok || err != nil {
|
||||
return ok, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
case <-time.After(retryDelay):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flock) setFh(flag int) error {
|
||||
// open a new os.File instance
|
||||
fh, err := os.OpenFile(f.path, flag, f.perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the file handle on the struct
|
||||
f.fh = fh
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetFh resets file handle:
|
||||
// - tries to close the file (ignore errors)
|
||||
// - sets fh to nil.
|
||||
func (f *Flock) resetFh() {
|
||||
if f.fh == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = f.fh.Close()
|
||||
|
||||
f.fh = nil
|
||||
}
|
||||
|
||||
// ensure the file handle is closed if no lock is held.
|
||||
func (f *Flock) ensureFhState() {
|
||||
if f.l || f.r || f.fh == nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.resetFh()
|
||||
}
|
||||
|
||||
func (f *Flock) reset() {
|
||||
f.l = false
|
||||
f.r = false
|
||||
|
||||
f.resetFh()
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Copyright 2018-2025 The Gofrs. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!unix && !windows) || plan9
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
func (f *Flock) Lock() error {
|
||||
return &fs.PathError{
|
||||
Op: "Lock",
|
||||
Path: f.Path(),
|
||||
Err: errors.ErrUnsupported,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flock) RLock() error {
|
||||
return &fs.PathError{
|
||||
Op: "RLock",
|
||||
Path: f.Path(),
|
||||
Err: errors.ErrUnsupported,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flock) Unlock() error {
|
||||
return &fs.PathError{
|
||||
Op: "Unlock",
|
||||
Path: f.Path(),
|
||||
Err: errors.ErrUnsupported,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return false, f.Lock()
|
||||
}
|
||||
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return false, f.RLock()
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Copyright 2018-2025 The Gofrs. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Lock is a blocking call to try and take an exclusive file lock.
|
||||
// It will wait until it is able to obtain the exclusive file lock.
|
||||
// It's recommended that TryLock() be used over this function.
|
||||
// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already exclusive-locked,
|
||||
// this function short-circuits and returns immediately assuming it can take the mutex lock.
|
||||
//
|
||||
// If the *Flock has a shared lock (RLock),
|
||||
// this may transparently replace the shared lock with an exclusive lock on some UNIX-like operating systems.
|
||||
// Be careful when using exclusive locks in conjunction with shared locks (RLock()),
|
||||
// because calling Unlock() may accidentally release the exclusive lock that was once a shared lock.
|
||||
func (f *Flock) Lock() error {
|
||||
return f.lock(&f.l, unix.LOCK_EX)
|
||||
}
|
||||
|
||||
// RLock is a blocking call to try and take a shared file lock.
|
||||
// It will wait until it is able to obtain the shared file lock.
|
||||
// It's recommended that TryRLock() be used over this function.
|
||||
// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already shared-locked,
|
||||
// this function short-circuits and returns immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) RLock() error {
|
||||
return f.lock(&f.r, unix.LOCK_SH)
|
||||
}
|
||||
|
||||
func (f *Flock) lock(locked *bool, flag int) error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(f.flag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
err := unix.Flock(int(f.fh.Fd()), flag)
|
||||
if err != nil {
|
||||
shouldRetry, reopenErr := f.reopenFDOnError(err)
|
||||
if reopenErr != nil {
|
||||
return reopenErr
|
||||
}
|
||||
|
||||
if !shouldRetry {
|
||||
return err
|
||||
}
|
||||
|
||||
err = unix.Flock(int(f.fh.Fd()), flag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*locked = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock is a function to unlock the file.
|
||||
// This file takes a RW-mutex lock,
|
||||
// so while it is running the Locked() and RLocked() functions will be blocked.
|
||||
//
|
||||
// This function short-circuits if we are unlocked already.
|
||||
// If not, it calls unix.LOCK_UN on the file and closes the file descriptor.
|
||||
// It does not remove the file from disk. It's up to your application to do.
|
||||
//
|
||||
// Please note,
|
||||
// if your shared lock became an exclusive lock,
|
||||
// this may unintentionally drop the exclusive lock if called by the consumer that believes they have a shared lock.
|
||||
// Please see Lock() for more details.
|
||||
func (f *Flock) Unlock() error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
// If we aren't locked or if the lockfile instance is nil
|
||||
// just return a nil error because we are unlocked.
|
||||
if (!f.l && !f.r) || f.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mark the file as unlocked.
|
||||
err := unix.Flock(int(f.fh.Fd()), unix.LOCK_UN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.reset()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLock is the preferred function for taking an exclusive file lock.
|
||||
// This function takes an RW-mutex lock before it tries to lock the file,
|
||||
// so there is the possibility that this function may block for a short time
|
||||
// if another goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking.
|
||||
// If we are unable to get the exclusive file lock,
|
||||
// the function will return false instead of waiting for the lock.
|
||||
// If we get the lock, we also set the *Flock instance as being exclusive-locked.
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return f.try(&f.l, unix.LOCK_EX)
|
||||
}
|
||||
|
||||
// TryRLock is the preferred function for taking a shared file lock.
|
||||
// This function takes an RW-mutex lock before it tries to lock the file,
|
||||
// so there is the possibility that this function may block for a short time
|
||||
// if another goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking.
|
||||
// If we are unable to get the shared file lock,
|
||||
// the function will return false instead of waiting for the lock.
|
||||
// If we get the lock, we also set the *Flock instance as being share-locked.
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return f.try(&f.r, unix.LOCK_SH)
|
||||
}
|
||||
|
||||
func (f *Flock) try(locked *bool, flag int) (bool, error) {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(f.flag); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
var retried bool
|
||||
|
||||
retry:
|
||||
err := unix.Flock(int(f.fh.Fd()), flag|unix.LOCK_NB)
|
||||
|
||||
switch {
|
||||
case errors.Is(err, unix.EWOULDBLOCK):
|
||||
return false, nil
|
||||
case err == nil:
|
||||
*locked = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !retried {
|
||||
shouldRetry, reopenErr := f.reopenFDOnError(err)
|
||||
if reopenErr != nil {
|
||||
return false, reopenErr
|
||||
} else if shouldRetry {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// reopenFDOnError determines whether we should reopen the file handle in readwrite mode and try again.
|
||||
// This comes from `util-linux/sys-utils/flock.c`:
|
||||
// > Since Linux 3.4 (commit 55725513)
|
||||
// > Probably NFSv4 where flock() is emulated by fcntl().
|
||||
// > https://github.com/util-linux/util-linux/blob/198e920aa24743ef6ace4e07cf6237de527f9261/sys-utils/flock.c#L374-L390
|
||||
func (f *Flock) reopenFDOnError(err error) (bool, error) {
|
||||
if !errors.Is(err, unix.EIO) && !errors.Is(err, unix.EBADF) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
st, err := f.fh.Stat()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if st.Mode()&f.perm != f.perm {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
f.resetFh()
|
||||
|
||||
// reopen in read-write mode and set the file handle
|
||||
err = f.setFh(f.flag | os.O_RDWR)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Copyright 2018-2025 The Gofrs. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This code implements the filelock API using POSIX 'fcntl' locks,
|
||||
// which attach to an (inode, process) pair rather than a file descriptor.
|
||||
// To avoid unlocking files prematurely when the same file is opened through different descriptors,
|
||||
// we allow only one read-lock at a time.
|
||||
//
|
||||
// This code is adapted from the Go package (go.22):
|
||||
// https://github.com/golang/go/blob/release-branch.go1.22/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go
|
||||
|
||||
//go:build aix || (solaris && !illumos)
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L28
|
||||
type lockType int16
|
||||
|
||||
// String returns the name of the function corresponding to lt
|
||||
// (Lock, RLock, or Unlock).
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock.go#L67
|
||||
func (lt lockType) String() string {
|
||||
switch lt {
|
||||
case readLock:
|
||||
return "RLock"
|
||||
case writeLock:
|
||||
return "Lock"
|
||||
default:
|
||||
return "Unlock"
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L30-L33
|
||||
const (
|
||||
readLock lockType = unix.F_RDLCK
|
||||
writeLock lockType = unix.F_WRLCK
|
||||
)
|
||||
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L35
|
||||
type inode = uint64
|
||||
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L37-L40
|
||||
type inodeLock struct {
|
||||
owner *Flock
|
||||
queue []<-chan *Flock
|
||||
}
|
||||
|
||||
type cmdType int
|
||||
|
||||
const (
|
||||
tryLock cmdType = unix.F_SETLK
|
||||
waitLock cmdType = unix.F_SETLKW
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
inodes = map[*Flock]inode{}
|
||||
locks = map[inode]inodeLock{}
|
||||
)
|
||||
|
||||
// Lock is a blocking call to try and take an exclusive file lock.
|
||||
// It will wait until it is able to obtain the exclusive file lock.
|
||||
// It's recommended that TryLock() be used over this function.
|
||||
// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already exclusive-locked, this function short-circuits and
|
||||
// returns immediately assuming it can take the mutex lock.
|
||||
//
|
||||
// If the *Flock has a shared lock (RLock),
|
||||
// this may transparently replace the shared lock with an exclusive lock on some UNIX-like operating systems.
|
||||
// Be careful when using exclusive locks in conjunction with shared locks (RLock()),
|
||||
// because calling Unlock() may accidentally release the exclusive lock that was once a shared lock.
|
||||
func (f *Flock) Lock() error {
|
||||
return f.lock(&f.l, writeLock)
|
||||
}
|
||||
|
||||
// RLock is a blocking call to try and take a shared file lock.
|
||||
// It will wait until it is able to obtain the shared file lock.
|
||||
// It's recommended that TryRLock() be used over this function.
|
||||
// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already shared-locked, this function short-circuits and
|
||||
// returns immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) RLock() error {
|
||||
return f.lock(&f.r, readLock)
|
||||
}
|
||||
|
||||
func (f *Flock) lock(locked *bool, flag lockType) error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(f.flag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
_, err := f.doLock(waitLock, flag, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*locked = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L48
|
||||
func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) {
|
||||
// POSIX locks apply per inode and process,
|
||||
// and the lock for an inode is released when *any* descriptor for that inode is closed.
|
||||
// So we need to synchronize access to each inode internally,
|
||||
// and must serialize lock and unlock calls that refer to the same inode through different descriptors.
|
||||
fi, err := f.fh.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Note(ldez): don't replace `syscall.Stat_t` by `unix.Stat_t` because `FileInfo.Sys()` returns `syscall.Stat_t`
|
||||
ino := fi.Sys().(*syscall.Stat_t).Ino
|
||||
|
||||
mu.Lock()
|
||||
|
||||
if i, dup := inodes[f]; dup && i != ino {
|
||||
mu.Unlock()
|
||||
return false, &fs.PathError{
|
||||
Op: lt.String(),
|
||||
Path: f.Path(),
|
||||
Err: errors.New("inode for file changed since last Lock or RLock"),
|
||||
}
|
||||
}
|
||||
|
||||
inodes[f] = ino
|
||||
|
||||
var wait chan *Flock
|
||||
|
||||
l := locks[ino]
|
||||
|
||||
switch {
|
||||
case l.owner == f:
|
||||
// This file already owns the lock, but the call may change its lock type.
|
||||
case l.owner == nil:
|
||||
// No owner: it's ours now.
|
||||
l.owner = f
|
||||
|
||||
case !blocking:
|
||||
// Already owned: cannot take the lock.
|
||||
mu.Unlock()
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
// Already owned: add a channel to wait on.
|
||||
wait = make(chan *Flock)
|
||||
l.queue = append(l.queue, wait)
|
||||
}
|
||||
|
||||
locks[ino] = l
|
||||
|
||||
mu.Unlock()
|
||||
|
||||
if wait != nil {
|
||||
wait <- f
|
||||
}
|
||||
|
||||
// Spurious EDEADLK errors arise on platforms that compute deadlock graphs at
|
||||
// the process, rather than thread, level. Consider processes P and Q, with
|
||||
// threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be
|
||||
// reported as a deadlock on systems that consider only process granularity:
|
||||
//
|
||||
// P.1 locks file A.
|
||||
// Q.3 locks file B.
|
||||
// Q.3 blocks on file A.
|
||||
// P.2 blocks on file B. (This is erroneously reported as a deadlock.)
|
||||
// P.1 unlocks file A.
|
||||
// Q.3 unblocks and locks file A.
|
||||
// Q.3 unlocks files A and B.
|
||||
// P.2 unblocks and locks file B.
|
||||
// P.2 unlocks file B.
|
||||
//
|
||||
// These spurious errors were observed in practice on AIX and Solaris in
|
||||
// cmd/go: see https://golang.org/issue/32817.
|
||||
//
|
||||
// We work around this bug by treating EDEADLK as always spurious. If there
|
||||
// really is a lock-ordering bug between the interacting processes, it will
|
||||
// become a livelock instead, but that's not appreciably worse than if we had
|
||||
// a proper flock implementation (which generally does not even attempt to
|
||||
// diagnose deadlocks).
|
||||
//
|
||||
// In the above example, that changes the trace to:
|
||||
//
|
||||
// P.1 locks file A.
|
||||
// Q.3 locks file B.
|
||||
// Q.3 blocks on file A.
|
||||
// P.2 spuriously fails to lock file B and goes to sleep.
|
||||
// P.1 unlocks file A.
|
||||
// Q.3 unblocks and locks file A.
|
||||
// Q.3 unlocks files A and B.
|
||||
// P.2 wakes up and locks file B.
|
||||
// P.2 unlocks file B.
|
||||
//
|
||||
// We know that the retry loop will not introduce a *spurious* livelock
|
||||
// because, according to the POSIX specification, EDEADLK is only to be
|
||||
// returned when “the lock is blocked by a lock from another process”.
|
||||
// If that process is blocked on some lock that we are holding, then the
|
||||
// resulting livelock is due to a real deadlock (and would manifest as such
|
||||
// when using, for example, the flock implementation of this package).
|
||||
// If the other process is *not* blocked on some other lock that we are
|
||||
// holding, then it will eventually release the requested lock.
|
||||
|
||||
nextSleep := 1 * time.Millisecond
|
||||
const maxSleep = 500 * time.Millisecond
|
||||
for {
|
||||
err = setlkw(f.fh.Fd(), cmd, lt)
|
||||
if !errors.Is(err, unix.EDEADLK) {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(nextSleep)
|
||||
|
||||
nextSleep += nextSleep
|
||||
if nextSleep > maxSleep {
|
||||
nextSleep = maxSleep
|
||||
}
|
||||
// Apply 10% jitter to avoid synchronizing collisions when we finally unblock.
|
||||
nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
f.doUnlock()
|
||||
|
||||
if cmd == tryLock && errors.Is(err, unix.EACCES) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, &fs.PathError{
|
||||
Op: lt.String(),
|
||||
Path: f.Path(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *Flock) Unlock() error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
// If we aren't locked or if the lockfile instance is nil
|
||||
// just return a nil error because we are unlocked.
|
||||
if (!f.l && !f.r) || f.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := f.doUnlock(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.reset()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L163
|
||||
func (f *Flock) doUnlock() (err error) {
|
||||
var owner *Flock
|
||||
|
||||
mu.Lock()
|
||||
|
||||
ino, ok := inodes[f]
|
||||
if ok {
|
||||
owner = locks[ino].owner
|
||||
}
|
||||
|
||||
mu.Unlock()
|
||||
|
||||
if owner == f {
|
||||
err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
|
||||
l := locks[ino]
|
||||
|
||||
if len(l.queue) == 0 {
|
||||
// No waiters: remove the map entry.
|
||||
delete(locks, ino)
|
||||
} else {
|
||||
// The first waiter is sending us their file now.
|
||||
// Receive it and update the queue.
|
||||
l.owner = <-l.queue[0]
|
||||
l.queue = l.queue[1:]
|
||||
locks[ino] = l
|
||||
}
|
||||
|
||||
delete(inodes, f)
|
||||
|
||||
mu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// TryLock is the preferred function for taking an exclusive file lock.
|
||||
// This function takes an RW-mutex lock before it tries to lock the file,
|
||||
// so there is the possibility that this function may block for a short time
|
||||
// if another goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking.
|
||||
// If we are unable to get the exclusive file lock,
|
||||
// the function will return false instead of waiting for the lock.
|
||||
// If we get the lock, we also set the *Flock instance as being exclusive-locked.
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return f.try(&f.l, writeLock)
|
||||
}
|
||||
|
||||
// TryRLock is the preferred function for taking a shared file lock.
|
||||
// This function takes an RW-mutex lock before it tries to lock the file,
|
||||
// so there is the possibility that this function may block for a short time
|
||||
// if another goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking.
|
||||
// If we are unable to get the shared file lock,
|
||||
// the function will return false instead of waiting for the lock.
|
||||
// If we get the lock, we also set the *Flock instance as being share-locked.
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return f.try(&f.r, readLock)
|
||||
}
|
||||
|
||||
func (f *Flock) try(locked *bool, flag lockType) (bool, error) {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(f.flag); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
hasLock, err := f.doLock(tryLock, flag, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
*locked = hasLock
|
||||
|
||||
return hasLock, nil
|
||||
}
|
||||
|
||||
// setlkw calls FcntlFlock with cmd for the entire file indicated by fd.
|
||||
// https://github.com/golang/go/blob/09aeb6e33ab426eff4676a3baf694d5a3019e9fc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go#L198
|
||||
func setlkw(fd uintptr, cmd cmdType, lt lockType) error {
|
||||
for {
|
||||
err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{
|
||||
Type: int16(lt),
|
||||
Whence: io.SeekStart,
|
||||
Start: 0,
|
||||
Len: 0, // All bytes.
|
||||
})
|
||||
if !errors.Is(err, unix.EINTR) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
// Copyright 2015 Tim Heckman. All rights reserved.
|
||||
// Copyright 2018-2025 The Gofrs. All rights reserved.
|
||||
// Use of this source code is governed by the BSD 3-Clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows `LockFileEX` docs,
|
||||
// which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as:
|
||||
//
|
||||
// > The function requests an exclusive lock. Otherwise, it requests a shared lock.
|
||||
//
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
||||
const winLockfileSharedLock = 0x00000000
|
||||
|
||||
// ErrorLockViolation is the error code returned from the Windows syscall when a lock would block,
|
||||
// and you ask to fail immediately.
|
||||
//
|
||||
//nolint:errname // It should be renamed to `ErrLockViolation`.
|
||||
const ErrorLockViolation windows.Errno = 0x21 // 33
|
||||
|
||||
// Lock is a blocking call to try and take an exclusive file lock.
|
||||
// It will wait until it is able to obtain the exclusive file lock.
|
||||
// It's recommended that TryLock() be used over this function.
|
||||
// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already locked, this function short-circuits and
|
||||
// returns immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) Lock() error {
|
||||
return f.lock(&f.l, windows.LOCKFILE_EXCLUSIVE_LOCK)
|
||||
}
|
||||
|
||||
// RLock is a blocking call to try and take a shared file lock.
|
||||
// It will wait until it is able to obtain the shared file lock.
|
||||
// It's recommended that TryRLock() be used over this function.
|
||||
// This function may block the ability to query the current Locked() or RLocked() status due to a RW-mutex lock.
|
||||
//
|
||||
// If we are already locked, this function short-circuits and
|
||||
// returns immediately assuming it can take the mutex lock.
|
||||
func (f *Flock) RLock() error {
|
||||
return f.lock(&f.r, winLockfileSharedLock)
|
||||
}
|
||||
|
||||
func (f *Flock) lock(locked *bool, flag uint32) error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(f.flag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
err := windows.LockFileEx(windows.Handle(f.fh.Fd()), flag, 0, 1, 0, &windows.Overlapped{})
|
||||
if err != nil && !errors.Is(err, windows.Errno(0)) {
|
||||
return err
|
||||
}
|
||||
|
||||
*locked = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock is a function to unlock the file.
|
||||
// This file takes a RW-mutex lock,
|
||||
// so while it is running the Locked() and RLocked() functions will be blocked.
|
||||
//
|
||||
// This function short-circuits if we are unlocked already.
|
||||
// If not, it calls UnlockFileEx() on the file and closes the file descriptor.
|
||||
// It does not remove the file from disk.
|
||||
// It's up to your application to do.
|
||||
func (f *Flock) Unlock() error {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
// if we aren't locked or if the lockfile instance is nil
|
||||
// just return a nil error because we are unlocked
|
||||
if (!f.l && !f.r) || f.fh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mark the file as unlocked
|
||||
err := windows.UnlockFileEx(windows.Handle(f.fh.Fd()), 0, 1, 0, &windows.Overlapped{})
|
||||
if err != nil && !errors.Is(err, windows.Errno(0)) {
|
||||
return err
|
||||
}
|
||||
|
||||
f.reset()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLock is the preferred function for taking an exclusive file lock.
|
||||
// This function does take a RW-mutex lock before it tries to lock the file,
|
||||
// so there is the possibility that this function may block for a short time
|
||||
// if another goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking.
|
||||
// If we are unable to get the exclusive file lock,
|
||||
// the function will return false instead of waiting for the lock.
|
||||
// If we get the lock, we also set the *Flock instance as being exclusive-locked.
|
||||
func (f *Flock) TryLock() (bool, error) {
|
||||
return f.try(&f.l, windows.LOCKFILE_EXCLUSIVE_LOCK)
|
||||
}
|
||||
|
||||
// TryRLock is the preferred function for taking a shared file lock.
|
||||
// This function does take a RW-mutex lock before it tries to lock the file,
|
||||
// so there is the possibility that this function may block for a short time if another goroutine is trying to take any action.
|
||||
//
|
||||
// The actual file lock is non-blocking.
|
||||
// If we are unable to get the shared file lock,
|
||||
// the function will return false instead of waiting for the lock.
|
||||
// If we get the lock, we also set the *Flock instance as being shared-locked.
|
||||
func (f *Flock) TryRLock() (bool, error) {
|
||||
return f.try(&f.r, winLockfileSharedLock)
|
||||
}
|
||||
|
||||
func (f *Flock) try(locked *bool, flag uint32) (bool, error) {
|
||||
f.m.Lock()
|
||||
defer f.m.Unlock()
|
||||
|
||||
if *locked {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if f.fh == nil {
|
||||
if err := f.setFh(f.flag); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer f.ensureFhState()
|
||||
}
|
||||
|
||||
err := windows.LockFileEx(windows.Handle(f.fh.Fd()), flag|windows.LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &windows.Overlapped{})
|
||||
if err != nil && !errors.Is(err, windows.Errno(0)) {
|
||||
if errors.Is(err, ErrorLockViolation) || errors.Is(err, windows.ERROR_IO_PENDING) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
*locked = true
|
||||
|
||||
return true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user