Help us shape the future of Go! Take the 2026 Go Developer Survey

flock

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

flock

A simple way to use flock and fcntl's OFD locks on linux

Installation

go get -u github.com/tfoertsch123/flock

Documentation

Overview

Package flock implements a simple interface to flock(2) and fcntl(2) open file description locks on Linux.

Lock Semantics

These locks are linked to file descriptors not processes. That means a lock can be inherited by child processes. It can be sent to unrelated processes via sendmsg(2) or similar. The file descriptor duplicated via dup(2) similarly inherits the lock. In all these cases, the lock is then shared by all file descriptors. If a lock is held this way by a number of file descriptors, it is released either when ANY one calls *Lock.Unlock or *Lock.UnlockRange (or the corresponding syscalls) or ALL of the file descriptors are closed.

These locks are advisory locks. They do not prevent any operation on the related files. The locks implemented by *Lock.LockEx, *Lock.LockSh and *Lock.Unlock represent exactly one lock per file. They are based on flock(2) and are somewhat portable across POSIX OS.

Range locks as provided by *Lock.LockRangeEx, *Lock.LockRangeSh and *Lock.UnlockRange are based on fcntl(2) and specific to Linux. They allow multiple independent locks on the same file descriptor.

fcntl(2) implements similar process based locks, see F_SETLK, F_SETLKW, and F_GETLK on the man page. These locks are NOT implemented by this package because their semantic is different.

Range Locks

Range locks are good if you want to tie multiple locks to the same file. Every file position can be an independent lock. The lock range is not related to the file size. The file can have a size of 0 while you lock the range from 100 to 200. If you just need a single lock file, use flock(2) based lock.

Blocking

This package has Lock... functions and TryLock... versions. They behave the same except that the Lock version waits until the lock becomes available while TryLock returns an error in this case.

In most cases, you likely don't want to use the blocking version in a Go program.

There is no context.Context based version or any other way to wait for a lock with a timeout. The only reliable way to implement that is by polling.

Ownership

A Lock object might or might not OWN its file descriptor. If the object owns it, then the file descriptor is closed when *Lock.Close is called. Otherwise, the owner of the descriptor is responsible for closing it. See *Lock.File and WithFd for more information.

As a general rule, the Lock object owns the file descriptor if it has opened it.

Thread safety

The internal data structure is NOT protected by any means.

Example (Flock)
//go:build linux

/*
   Copyright 2026 Torsten Foertsch

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package main

import (
	"fmt"
	"github.com/tfoertsch123/flock"
	"log"
	"os"
	"os/exec"
	"sync"
)

func start(cmdline string) *exec.Cmd {
	cmd := exec.Command("sh", "-c", cmdline)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		log.Fatalf(`"%s" failed: %v`, cmdline, err)
	}
	return cmd
}

func main() {
	var wg sync.WaitGroup

	lck := flock.New(
		flock.WithPath("."),
		flock.WithIsDir(), // require path to be a directory
	)

	// Open the file/directory and acquire a shared lock.
	if err := lck.TryLockSh(); err != nil {
		log.Fatalf(`Couldn't lock(p1) .: %v`, err)
	}

	// The shell inherits the lock. (no WithCloseOnExec())
	p1 := start("sleep .5; echo P1: slept")
	wg.Go(func() { p1.Wait() })

	// this only closes the file. It does not release the lock
	lck.Close()

	lck = flock.New(
		flock.WithPath("."),
	)

	// Just open, no lock
	if err := lck.Open(); err != nil {
		log.Fatalf(`Couldn't open(p2) .: %v`, err)
	}

	// "flock -s" at the shell level is the same as flock.LockSh().
	// This acquires a shared lock with does not conflict with the
	// lock held by p1. So, the "echo" will not wait for p1 to finish.
	p2 := start(fmt.Sprintf("flock -s %d; echo P2: lock acquired", lck.Fd()))
	wg.Go(func() { p2.Wait() })

	lck.Close()

	// Open the file yet another time
	lck = flock.New(
		flock.WithPath("."),
	)

	// Just open
	if err := lck.Open(); err != nil {
		log.Fatalf(`Couldn't open(p3) .: %v`, err)
	}

	// "flock -x" at the shell level is the same as flock.LockEx().
	// This acquires an exclusive lock with does conflict with the
	// lock held by p1. So, the "echo" will wait for p1 to finish.
	p3 := start(fmt.Sprintf("flock -x %d; echo P3: lock acquired", lck.Fd()))
	wg.Go(func() { p3.Wait() })

	lck.Close()

	wg.Wait()

}

// Local Variables:
// tab-width: 4
// End:
Output:
P2: lock acquired
P1: slept
P3: lock acquired

Index

Examples

Constants

View Source
const SEEK_CUR int16 = unix.SEEK_CUR
View Source
const SEEK_END int16 = unix.SEEK_END
View Source
const SEEK_SET int16 = unix.SEEK_SET

SEEK_SET, SEEK_CUR and SEEK_END represent the same values as used in Linux' lseek(2) syscall. They are used as parameters for range locks like *Lock.TryLockRangeEx.

Variables

View Source
var ErrAlreadyOpen error = errors.New("file is already open")

ErrAlreadyOpen is returned by *Lock.Open if the underlying file descriptor is deemed to be open. There is no syscall performed to actually check if the file descriptor is already open. Just if the stored file descriptor number is negative, the error is triggered.

Functions

This section is empty.

Types

type Lock

type Lock struct {
	// contains filtered or unexported fields
}

A Lock represents the lock object.

func New

func New(opt ...Option) *Lock

New creates the Lock object. At this time the file is not opened, yet. The file is only opened (unless an open file descriptor is passed), when either *Lock.Open is called or any of the Lock functions is called for the first time.

Example:

lck := flock.New(
    flock.WithPath("/my/lock/file"),
    flock.WithCreate(unix.S_IRUSR, unix.S_IWUSR),
    flock.WithRdWr(),
    flock.WithCloseOnExec(),
)

Note, if you use WithFd to pass an open file descriptor, that file descriptor will not be closed by *Lock.Close.

It is cheap to create these objects. No syscall involved.

func (*Lock) Close

func (l *Lock) Close() error

Close closes the underlying file descriptor if it is owned by the Lock object. It is not an error to close an already closed object. A subsequent *Lock.Open or any of the locking functions will re-open the file according to the parameters passed to New.

Note, Close does NOT unlock anything prior to closing the file. So, if the file descriptor is still open as in has been inherited by a child process or dup(2)ed or similar, the corresponding locks are still being held.

func (*Lock) Fd

func (l *Lock) Fd() int

Fd returns the underlying file descriptor number. If the file is closed, -1 is returned.

func (*Lock) File

func (l *Lock) File() *os.File

File returns an *os.File object representing the underlying file. Calling this function disowns the file descriptor. After that, the *os.File object is responsible for closing it. If the file descriptor is already closed nil is returned.

func (*Lock) LockEx

func (l *Lock) LockEx() error

LockEx exclusively locks the file using flock(2). If the file is not yet opened it is opened automatically. The returned error represents either an error in flock(2) itself or a failed open(2). On success nil is returned.

Note, this is a blocking syscall. If the lock is not available, it will block the calling GO routine. There is no good way to interrupt that syscall.

func (*Lock) LockRangeEx

func (l *Lock) LockRangeEx(start, len int64, whence ...int16) error

LockRangeEx exclusively locks the range from start to start+len-1. The position at start+len is not locked. So, LockRangeEx(10, 1) locks exactly 1 byte position.

If len==0, the range from the start position up to positive infinity is locked. This does not depend on the file size.

The optional whence parameter defines how to interpret the start parameter. If omitted, SEEK_SET is used. If multiple whence parameters are passed only the first one is used.

  • SEEK_SET: start is interpreted relative to the beginning of the file.
  • SEEK_CUR: start is interpreted relative to the current file position.
  • SEEK_END: start is interpreted relative to the current end of the file.

Both, start and len can be negative as long as the resulting starting position is not negative.

func (*Lock) LockRangeSh

func (l *Lock) LockRangeSh(start, len int64, whence ...int16) error

LockRangeSh acquires a shared lock. The parameters are the same as for *Lock.LockRangeEx

func (*Lock) LockSh

func (l *Lock) LockSh() error

LockSh represents a shared lock on the file using flock(2). If the file is not yet opened it is opened automatically. The returned error represents either an error in flock(2) itself or a failed open(2). On success nil is returned.

Note, this is a blocking syscall. If the lock is not available, it will block the calling GO routine. There is no good way to interrupt that syscall.

func (*Lock) Open

func (l *Lock) Open() error

Open opens the underlying file. If the file is deemed to be already open, ErrAlreadyOpen is returned. Otherwise, the underlying operating system error is returned. On success, nil is returned.

This function is called automatically when a locking function is called and the file is not yet open.

func (*Lock) OwningFd

func (l *Lock) OwningFd() bool

OwningFd can be used to determine if *Lock.Close will close the underlying file descriptor.

func (*Lock) Path

func (l *Lock) Path() string

Path returns the file path stored in the Lock object.

func (*Lock) TryLockEx

func (l *Lock) TryLockEx() error

TryLockEx is the non-blocking version of *Lock.LockEx. If the lock cannot be acquired unix.EWOULDBLOCK is returned.

func (*Lock) TryLockRangeEx

func (l *Lock) TryLockRangeEx(start, len int64, whence ...int16) error

TryLockRangeEx is the non-blocking version of *Lock.LockRangeEx. If the lock cannot be acquired unix.EWOULDBLOCK is returned.

func (*Lock) TryLockRangeSh

func (l *Lock) TryLockRangeSh(start, len int64, whence ...int16) error

TryLockRangeSh is the non-blocking version of LockRangeSh. The parameters are the same as for *Lock.LockRangeEx. If the lock cannot be acquired unix.EWOULDBLOCK is returned.

func (*Lock) TryLockSh

func (l *Lock) TryLockSh() error

TryLockSh is the non-blocking version of *Lock.LockSh. If the lock cannot be acquired unix.EWOULDBLOCK is returned.

func (*Lock) Unlock

func (l *Lock) Unlock() error

Unlock releases the lock. It is not an error to unlock an already released lock.

If the underlying file is closed, it will not be opened and nil is returned.

Any error comes from the underlying flock(2) syscall.

func (*Lock) UnlockRange

func (l *Lock) UnlockRange(start, len int64, whence ...int16) error

UnlockRange releases a range lock. The parameters are the same as for *Lock.LockRangeEx.

If you simply want to release all existing range locks for a file, you can do this:

lck.UnlockRange(0, 0)

This releases all locks no matter if exclusive or shared. Since len==0, the entire range from start up to infinity is released. Since start==0 and no whence parameter is passed implying SEEK_SET, this identifies the start of the file.

type Option

type Option func(*opts)

An Option represents an option passed to the constructor New. All options are functions starting with With...

func WithAppend

func WithAppend() Option

WithAppend corresponds to O_APPEND in open(2).

func WithCloseOnExec

func WithCloseOnExec() Option

WithCloseOnExec corresponds to O_CLOEXEC in open(2).

func WithCreate

func WithCreate(perms ...uint32) Option

WithCreate corresponds to O_CREAT in open(2). The parameters are ORed together to build the file mode. If completely omitted, the default file permissions are 0o600 or unix.S_IRUSR + unix.S_IWUSR or "rw-------".

The example below sets the file mode to 0o603 or "rw-----wx" (which does not make much sense, though)

Example:

lck := flock.New(
    flock.WithCreate(unix.S_IRUSR, unix.S_IWUSR, 3),
    ...
)

func WithFd

func WithFd(param int) Option

WithFd allows you to pass an open file descriptor to the constructor New. This can be combined with WithPath. However, the path in this case will never be used for anything. If a file descriptor is passed this way, it is not owned by the Lock object. So, *Lock.Close will not close the file. All the modifiers for opening the file, will also be ignored, WithRdWr, WithTruncate, ...

Passing a negative parameter, which is an invalid file descriptor, is ignored. *Lock.Fd will return -1.

func WithIsDir

func WithIsDir() Option

WithIsDir corresponds to O_DIRECTORY in open(2).

func WithPath

func WithPath(param string) Option

WithPath passes the name of the file, see New. This is optional. However, if no path is passed, a file descriptor number needs to be provided, see WithFd.

func WithPathAt added in v1.1.0

func WithPathAt(param int) Option

WithPathAt optionally passes a directory file descriptor relative to which the path passed with WithPath is interpreted.

func WithRdWr

func WithRdWr() Option

WithRdWr corresponds to O_RDWR in open(2).

func WithTruncate

func WithTruncate() Option

WithTruncate corresponds to O_TRUNC in open(2).

func WithWrOnly

func WithWrOnly() Option

WithWrOnly corresponds to O_WRONLY in open(2).

func WithoutIntrRetry

func WithoutIntrRetry() Option

WithoutIntrRetry turns off the internal retry mechanism if the syscall comes back with EINTR. Mainly, this applies to the blocking syscalls.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL