lock

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 11, 2026 License: AGPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package lock provides file locking capabilities for safe concurrent file operations.

Description

This package implements advisory file locking to prevent conflicts when the agent is editing files. It detects external modifications via fsnotify and supports stale lock cleanup via PID checks and TTL expiration.

Thread Safety

FileLockManager is safe for concurrent use. All public methods are synchronized.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrFileLocked indicates the file is already locked by another process.
	ErrFileLocked = errors.New("file is locked by another process")

	// ErrLockNotHeld indicates an attempt to release a lock not held by this manager.
	ErrLockNotHeld = errors.New("lock not held by this process")

	// ErrExternalModification indicates the file was modified while locked.
	ErrExternalModification = errors.New("file was modified externally while locked")

	// ErrLockExpired indicates the lock has passed its TTL.
	ErrLockExpired = errors.New("lock has expired")

	// ErrInvalidPath indicates an invalid file path was provided.
	ErrInvalidPath = errors.New("invalid file path")
)

Sentinel errors for lock operations.

Functions

func IsProcessAlive

func IsProcessAlive(pid int) bool

IsProcessAlive checks if a process with the given PID is still running.

Description

Used for stale lock detection. On Unix, uses kill -0. On Windows, uses OpenProcess.

Inputs

  • pid: Process ID to check.

Outputs

  • bool: True if process exists, false otherwise.

Platform Notes

This function is implemented in platform-specific files.

Types

type ChangeType

type ChangeType int

ChangeType indicates the type of external file change.

const (
	// ChangeWrite indicates the file content was modified.
	ChangeWrite ChangeType = iota
	// ChangeDelete indicates the file was deleted.
	ChangeDelete
	// ChangeRename indicates the file was renamed.
	ChangeRename
)

func (ChangeType) String

func (c ChangeType) String() string

String returns a human-readable name for the change type.

type ExternalChangeEvent

type ExternalChangeEvent struct {
	Path      string
	EventType ChangeType
}

ExternalChangeEvent represents a file modification detected by the watcher.

Description

Sent to callbacks when a locked file is modified externally. Includes the path and type of change detected.

Fields

  • Path: Absolute path to the modified file.
  • EventType: Type of change (write, delete, rename).

type ExternalModificationError

type ExternalModificationError struct {
	Path       string
	ChangeType ChangeType
}

ExternalModificationError provides details about an external file change.

Description

Wraps ErrExternalModification with information about what changed, allowing the caller to reload, abort, or ask the user.

Fields

  • Path: The file that was modified.
  • ChangeType: Type of modification (write, delete, rename).

func (*ExternalModificationError) Error

func (e *ExternalModificationError) Error() string

Error returns a human-readable error message.

func (*ExternalModificationError) Unwrap

func (e *ExternalModificationError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type FileLockError

type FileLockError struct {
	Path   string
	Holder *LockInfo
	Err    error
}

FileLockError provides detailed information about a lock conflict.

Description

Wraps ErrFileLocked with information about the current lock holder, allowing the caller to decide how to proceed (wait, abort, force).

Fields

  • Path: The file that is locked.
  • Holder: Information about the current lock holder.
  • Err: The underlying error (typically ErrFileLocked).

func (*FileLockError) Error

func (e *FileLockError) Error() string

Error returns a human-readable error message.

func (*FileLockError) Unwrap

func (e *FileLockError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type FileLockManager

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

FileLockManager manages file locks for safe concurrent file operations.

Description

Provides exclusive file locking with: - Advisory locks via syscall.Flock (Unix) or LockFileEx (Windows) - External change detection via fsnotify - Stale lock cleanup via PID checks and TTL expiration - Lock info files for debugging and visibility

Thread Safety

All public methods are safe for concurrent use from multiple goroutines.

func NewFileLockManager

func NewFileLockManager(config ManagerConfig) (*FileLockManager, error)

NewFileLockManager creates a new file lock manager.

Description

Creates a manager with the specified configuration. If CleanupOnInit is true, stale locks from crashed processes are cleaned up on creation.

Inputs

  • config: Manager configuration. Use DefaultManagerConfig() for defaults.

Outputs

  • *FileLockManager: Ready-to-use lock manager.
  • error: Non-nil if setup fails (e.g., can't create lock directory).

Example

config := lock.DefaultManagerConfig()
config.SessionID = "sess-abc123"
manager, err := lock.NewFileLockManager(config)

func (*FileLockManager) AcquireLock

func (m *FileLockManager) AcquireLock(filePath, reason string) error

AcquireLock acquires an exclusive lock on a file.

Description

Attempts to acquire an exclusive lock on the specified file. Non-blocking: returns immediately if the file is already locked. Creates a .lock info file in the lock directory for visibility.

Inputs

  • filePath: Absolute or relative path to the file to lock.
  • reason: Human-readable reason for the lock (for debugging).

Outputs

  • error: nil on success, FileLockError if already locked, other errors on failure.

Example

err := manager.AcquireLock("/path/to/file.go", "Applying patch for CB-15")
if err != nil {
    if errors.Is(err, lock.ErrFileLocked) {
        // Handle lock conflict
    }
    return err
}
defer manager.ReleaseLock("/path/to/file.go")

func (*FileLockManager) CleanupStaleLocks

func (m *FileLockManager) CleanupStaleLocks() (int, error)

CleanupStaleLocks removes locks from dead processes.

Description

Scans the lock directory for lock files from processes that have exited or locks that have expired. Removes stale lock files.

Outputs

  • int: Number of stale locks cleaned up.
  • error: Non-nil on failure to scan directory.

func (*FileLockManager) Close

func (m *FileLockManager) Close() error

Close shuts down the lock manager.

Description

Releases all locks and stops the file watcher. Should be called when the manager is no longer needed.

Outputs

  • error: First error encountered during shutdown.

func (*FileLockManager) IsLocked

func (m *FileLockManager) IsLocked(filePath string) (bool, *LockInfo, error)

IsLocked checks if a file is locked by any process.

Description

Checks both our internal state and lock info files to determine if a file is locked. Useful for pre-flight checks.

Inputs

  • filePath: Path to check.

Outputs

  • bool: True if file is locked.
  • *LockInfo: Information about the lock holder (nil if not locked).
  • error: Non-nil on failure to check.

func (*FileLockManager) RegisterCallback

func (m *FileLockManager) RegisterCallback(filePath string, callback func(ExternalChangeEvent))

RegisterCallback registers a callback for external file changes.

Description

The callback is invoked when a locked file is modified externally. Multiple callbacks can be registered for the same file.

Inputs

  • filePath: Path to monitor.
  • callback: Function to call on change.

func (*FileLockManager) ReleaseAll

func (m *FileLockManager) ReleaseAll() error

ReleaseAll releases all locks held by this manager.

Description

Releases all locks acquired by this manager. Should be called on session end or manager shutdown.

Outputs

  • error: First error encountered (continues releasing on error).

func (*FileLockManager) ReleaseLock

func (m *FileLockManager) ReleaseLock(filePath string) error

ReleaseLock releases a lock on a file.

Description

Releases a previously acquired lock. Safe to call on unlocked files (returns ErrLockNotHeld). Removes the .lock info file.

Inputs

  • filePath: Path to the file to unlock (must match path used in AcquireLock).

Outputs

  • error: nil on success, ErrLockNotHeld if not locked by this manager.

func (*FileLockManager) WatchFile

func (m *FileLockManager) WatchFile(ctx context.Context, filePath string, callback func(string))

WatchFile starts watching a file for external changes.

Description

Watches the specified file and calls the callback when changes are detected. Stops watching when the context is cancelled.

Inputs

  • ctx: Context for cancellation.
  • filePath: File to watch.
  • callback: Function to call on change.

type FileLocker

type FileLocker interface {
	// Lock acquires an exclusive lock on the file.
	//
	// # Description
	//
	// Attempts to acquire an exclusive (write) lock on the file.
	// Non-blocking: returns immediately if lock cannot be acquired.
	//
	// # Inputs
	//
	//   - f: Open file handle to lock.
	//
	// # Outputs
	//
	//   - error: nil on success, ErrFileLocked if already locked.
	Lock(f *os.File) error

	// Unlock releases the lock on the file.
	//
	// # Description
	//
	// Releases a previously acquired lock. Safe to call even if not locked.
	//
	// # Inputs
	//
	//   - f: Open file handle to unlock.
	//
	// # Outputs
	//
	//   - error: nil on success, error on system failure.
	Unlock(f *os.File) error
}

FileLocker abstracts platform-specific file locking operations.

Description

Provides a unified interface for file locking across Unix and Windows. Unix uses syscall.Flock, Windows uses LockFileEx.

Thread Safety

Implementations must be safe for concurrent use on different files. Locking the same file from multiple goroutines is undefined behavior.

type LockInfo

type LockInfo struct {
	FilePath  string `json:"file_path"`
	PID       int    `json:"pid"`
	SessionID string `json:"session_id"`
	LockedAt  int64  `json:"locked_at"`  // Unix milliseconds UTC
	ExpiresAt int64  `json:"expires_at"` // Unix milliseconds UTC
	Reason    string `json:"reason"`
}

LockInfo contains metadata about a held lock.

Description

Stored in JSON format in .aleutian/locks/<hash>.lock files for visibility and debugging. Includes PID for stale lock detection.

Fields

  • FilePath: Absolute path to the locked file.
  • PID: Process ID of the lock holder.
  • SessionID: Aleutian session ID for correlation.
  • LockedAt: When the lock was acquired.
  • ExpiresAt: When the lock expires (for stale detection).
  • Reason: Human-readable reason for the lock.

func (*LockInfo) IsExpired

func (l *LockInfo) IsExpired() bool

IsExpired checks if the lock has passed its TTL.

Description

Returns true if the current time is after ExpiresAt. Expired locks are treated as stale and can be cleaned up.

Outputs

  • bool: True if the lock has expired.

type ManagerConfig

type ManagerConfig struct {
	LockDir       string
	SessionID     string
	DefaultTTL    time.Duration
	CleanupOnInit bool
}

ManagerConfig configures the FileLockManager behavior.

Description

Allows customization of lock directory, TTL, and session identification. All fields have sensible defaults if not specified.

Fields

  • LockDir: Directory for lock files (default: .aleutian/locks).
  • SessionID: Session identifier for lock ownership.
  • DefaultTTL: Default lock expiration time (default: 1 hour).
  • CleanupOnInit: Run stale lock cleanup on initialization.

func DefaultManagerConfig

func DefaultManagerConfig() ManagerConfig

DefaultManagerConfig returns a ManagerConfig with sensible defaults.

Description

Creates a configuration suitable for most use cases. LockDir defaults to .aleutian/locks, TTL defaults to 1 hour.

Outputs

  • ManagerConfig: Configuration with default values.

type RaceConditionError

type RaceConditionError struct {
	Path   string
	Reason string
}

RaceConditionError indicates a race between lock acquisition and file change.

Description

Occurs when a file is modified between starting to watch and acquiring the lock. The safe response is to re-read and retry.

Fields

  • Path: The file with the race condition.
  • Reason: Description of what happened.

func (*RaceConditionError) Error

func (e *RaceConditionError) Error() string

Error returns a human-readable error message.

type UnixFileLocker

type UnixFileLocker struct{}

UnixFileLocker implements FileLocker using syscall.Flock.

Description

Uses advisory file locking via flock(2). Locks are: - Process-scoped (inherited by child processes) - Released on file close or process exit - Non-blocking when LOCK_NB is specified

Thread Safety

Safe for concurrent use on different files.

func (*UnixFileLocker) Lock

func (l *UnixFileLocker) Lock(f *os.File) error

Lock acquires an exclusive lock using flock(2).

Description

Uses LOCK_EX|LOCK_NB for non-blocking exclusive lock. Returns immediately if the file is already locked.

Inputs

  • f: Open file handle to lock.

Outputs

  • error: nil on success, ErrFileLocked if already locked by another process.

func (*UnixFileLocker) Unlock

func (l *UnixFileLocker) Unlock(f *os.File) error

Unlock releases the lock using flock(2).

Description

Uses LOCK_UN to release the lock. Safe to call even if not locked.

Inputs

  • f: Open file handle to unlock.

Outputs

  • error: nil on success, error on system failure.

Jump to

Keyboard shortcuts

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