flock

package
v0.19.2 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 3 Imported by: 0

README

flock

A non-blocking exclusive file lock for the SDK kernel. Stdlib-only.

import "github.com/kitsunium/sdk/internal/kernel/fs/flock"

if !flock.Native {
	return refuseThisPlatform() // your own code, at construction
}
for {
	held, err := flock.TryLock(file)
	if err != nil {
		return wrap(err) // the kernel's errno, never "busy"
	}
	if held {
		break
	}
	waitOnYourClock(ctx) // the primitive never blocks; you poll
}
defer flock.Unlock(file)

TryLock takes an exclusive lock on the whole of a file and answers at once: held, held elsewhere (false, nil — an answer, never an error), or a failure of the call (the errno, unwrapped). There is no blocking variant: a blocking flock(2), and a LockFileEx without LOCKFILE_FAIL_IMMEDIATELY, park the thread inside a syscall no context cancellation can reach, so a caller with a deadline polls on its own clock instead.

GOOS Primitive Native
linux, darwin, freebsd, openbsd, netbsd, dragonfly (android, ios through their tags) flock(LOCK_EX|LOCK_NB) — advisory, per open file description true
windows LockFileEx over every byte of the file, LOCKFILE_FAIL_IMMEDIATELY — mandatory true
js, wasip1, plan9, aix, solaris, illumos none: errors.ErrUnsupported from every call false

The two kernels agree on what excludes processes — a second open description of the file is refused while the first holds the lock, and a holder's death releases it — and are OPPOSITES on a second lock through the same description: flock(2) converts it and returns at once, LockFileEx refuses it. So a caller that shares one descriptor between goroutines puts a gate of its own in front of TryLock; the primitive promises nothing there.

See CLAUDE.md for the measurements, the consumers, and what the package deliberately does not do (ADR 0081, ADR 0159).

Documentation

Overview

Package flock takes an exclusive lock on a whole file WITHOUT waiting for it, and gives it back: flock(2) on the Unix kernels that have it, LockFileEx over every byte of the file on Windows (ADR 0081, ADR 0159).

Never a blocking call

TryLock answers at once — held, held elsewhere, or a failure of the call — and there is no blocking variant. That is the point of the package rather than a gap in it. A blocking flock(2), and a LockFileEx without LOCKFILE_FAIL_IMMEDIATELY, park the calling thread inside a syscall no context cancellation can reach: a caller whose deadline expired would keep waiting for a lock it no longer wants, and its goroutine would not come back until some other process let go. A caller that must wait polls TryLock on a clock of its own, which is how every file lock in the SDK waits (ADR 0052, ADR 0073).

One contract over two primitives, and the row where they are opposites

Both kernels give the same answer where exclusion between PROCESSES is decided: a second open description of the file — another process, or a second os.OpenFile of the same path in this one — is refused while the first holds the lock, and the holder's death releases it. They give OPPOSITE answers to a second lock taken through the SAME description: flock(2) converts it and returns at once, LockFileEx refuses it. So neither kernel excludes the goroutines of one process that share a descriptor in a way a caller could rely on everywhere, and this package does not pretend to: a caller that shares one descriptor between goroutines puts a gate of its own in front of TryLock. TestTheSameDescriptionIsConvertedOnUnixAndRefusedOnWindows pins both answers.

The Windows lock is also MANDATORY where flock(2) is advisory, and it covers a byte range — here every byte of the file. flock_windows.go states each difference and what it was measured on; what a caller builds on them is the caller's to say.

Where neither exists

Native is false on every GOOS with neither primitive — js, wasip1, plan9, aix, solaris, illumos — and there TryLock and Unlock return errors.ErrUnsupported. A caller reads Native where it is wired and refuses there, under its own code (ADR 0018 §(a)); the package compiles on every GOOS so that the refusal can be written at all.

What it returns

The kernel's own answer. Contention is (false, nil), never an error; any other failure is the errno itself, unwrapped, so a caller wraps it once in its own vocabulary — the convention pathchain set for this family. The package owns no error code.

Package flock — flock(2), on the kernels that have it.

flock(2) is not POSIX. It exists on Linux and on every BSD (macOS included) with the same shape, and the tag set above is exactly where Go's syscall package declares it — android and ios reach this file through the linux and darwin tags. The lock belongs to the open file DESCRIPTION: a dup(2)'d descriptor shares it, a second open(2) of the same path does not, and the kernel drops it when the last descriptor on the description closes — which a process's death does. It is ADVISORY: it binds every process that takes it, and nothing else.

Index

Constants

View Source
const Native bool = true

Native reports that this GOOS has a file lock: flock(2), here.

Variables

This section is empty.

Functions

func TryLock

func TryLock(file *os.File) (held bool, err error)

TryLock takes an exclusive lock on the whole of file without waiting.

It reports (true, nil) when the lock is now held through file, (false, nil) when another open description holds it, and (false, err) when the call itself failed: the kernel's errno, or errors.ErrUnsupported where Native is false. Contention is an answer and never an error, so a caller cannot mistake a busy lock for a broken one, nor the reverse.

func Unlock

func Unlock(file *os.File) error

Unlock releases the lock TryLock took through file.

Closing the descriptor would release it too. Unlocking explicitly first keeps the two events apart on purpose: the lock is given up while the descriptor is still valid, so a failure to release is reportable rather than hidden behind a close that "worked".

Types

This section is empty.

Jump to

Keyboard shortcuts

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