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 ¶
- Variables
- func IsProcessAlive(pid int) bool
- type ChangeType
- type ExternalChangeEvent
- type ExternalModificationError
- type FileLockError
- type FileLockManager
- func (m *FileLockManager) AcquireLock(filePath, reason string) error
- func (m *FileLockManager) CleanupStaleLocks() (int, error)
- func (m *FileLockManager) Close() error
- func (m *FileLockManager) IsLocked(filePath string) (bool, *LockInfo, error)
- func (m *FileLockManager) RegisterCallback(filePath string, callback func(ExternalChangeEvent))
- func (m *FileLockManager) ReleaseAll() error
- func (m *FileLockManager) ReleaseLock(filePath string) error
- func (m *FileLockManager) WatchFile(ctx context.Context, filePath string, callback func(string))
- type FileLocker
- type LockInfo
- type ManagerConfig
- type RaceConditionError
- type UnixFileLocker
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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.
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 ¶
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.