touchfile

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2024 License: Apache-2.0 Imports: 8 Imported by: 0

README

test suite

touchfile

touchfile is a Go package that provides a mechanism for creating, locking, and managing touch files to coordinate access between different processes and goroutines. It uses file locks (flock) to ensure only one process or thread has exclusive access to a critical section at a time.

This package is designed for scenarios where multiple processes or threads need to coordinate on a shared resource (e.g., a file). It leverages mutexes for goroutine safety and file-based locks for process-level coordination.

Features

  • Mutex protection: Ensures safe access across multiple goroutines.
  • Process-level locking: Uses advisory locking (flock) for file-level coordination between different processes.
  • Support for both shared and exclusive locks: Choose between shared (read) locks and exclusive (write) locks.
  • Timeout handling with context: Automatically handle lock acquisition with context-based timeouts and cancellation.
  • Critical section helpers: Simplified WithLock function for running code within a lock-protected critical section.

Important notes

Non-reentrant

This package is not reentrant. It is up to the caller to avoid recursive calls that attempt to acquire the same lock, which would result in deadlocks.

File system compatibility

flock may not be compatible with all file systems (notably networked file systems like NFS). Please ensure your file system supports advisory file locks.

Installation

To install the package, run:

go get github.com/trufflesecurity/touchfile

In your Go module, import the package with:

import "github.com/trufflesecurity/touchfile"

Example usage

Basic usage
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

if err := tf.Lock(ctx); err != nil {
    log.Fatalf("failed to acquire lock: %v", err)
}
defer func() {
    if err := tf.Unlock(); err != nil {
        log.Printf("failed to release lock: %v", err)
    }
}()
Global lock using the program binary
program, err := os.Executable()
if err != nil {
  log.Fatalf("failed to determine executable path: %v", err)
}

tf, err := touchfile.NewTouchFile(program)
...
Using WithLock for a critical section
tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

err = tf.WithLock(ctx, func() error {
    fmt.Println("Doing some work while holding the lock")
    return nil
})
if err != nil {
    log.Fatalf("operation failed: %v", err)
}

API

NewTouchFile(path string) (*TouchFile, error)

Creates a new TouchFile instance using the specified file path. If the path is empty, a temporary file will be used.

Lock(ctx context.Context, lockType LockType) error

Acquires the specified lock (Shared or Exclusive) on the touch file. The function will attempt to acquire the lock until the context times out or is canceled.

Unlock() error

Releases the lock on the touch file.

WithLock(ctx context.Context, lockType LockType, f func() error) error

A convenience function that locks the touch file, executes the provided function, and then releases the lock. It ensures the lock is always released, even if the function returns an error.

Documentation

Overview

Package touchfile provides a mechanism to create, lock, and manage a touch file for coordinating access between different processes.

Mutexes are used to coordinate between goroutines. Touch files are used to coordinate between different processes. Because touch files aren't truly atomic, this package uses flock to acquire a voluntary lock on the file.

This package creates a temporary file (unless otherwise specified) to use as a lock file. A voluntary lock is acquired on the file using the flock system call.

WARNING: Like all go code related to concurrency, this module is NOT reentrant. And because go doesn't have a way to detect reentrancy, it's up to the caller to avoid deadlocks caused by concurrent access to this module.

WARNING: Because flock is used, this module may not be compatible with all file systems (notably networked file systems like NFS).

Example usage:

Basic usage:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

if err := tf.Lock(ctx); err != nil {
    log.Fatalf("failed to acquire lock: %v", err)
}
defer func() {
    if err := tf.Unlock(); err != nil {
        log.Printf("failed to release lock: %v", err)
    }
}()

Global lock using the program binary:

program, err := os.Executable()
if err != nil {
  log.Fatalf("failed to determine executable path: %v", err)
}

tf, err := touchfile.NewTouchFile(program)
...

Using WithLock for a critical section:

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

err = tf.WithLock(ctx, func() error {
    // Critical section
    fmt.Println("Doing some work while holding the lock")
    return nil
})
if err != nil {
    log.Fatalf("operation failed: %v", err)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type LockType

type LockType int

LockType is an enum for the type of lock to acquire on the touch file.

const (
	// An Exclusive lock prevents any other processes or threads from acquiring
	// any type of lock on the touch file.
	Exclusive LockType = iota

	// A Shared lock allows multiple processes or threads to acquire another
	// Shared lock on the touch file, while preventing an Exclusive lock.
	Shared
)

type TouchFile

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

TouchFile struct holds the path to the touch file and a flock instance for advisory locking. A mutex is included to ensure safe access across multiple goroutines.

func NewTouchFile

func NewTouchFile(path string) (*TouchFile, error)

NewTouchFile creates a TouchFile using flock for advisory locking. If the provided path is empty, a temporary file path will be used instead. The path is converted to an absolute path.

Example:

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

func (*TouchFile) ExclusiveLock

func (tf *TouchFile) ExclusiveLock(ctx context.Context) error

ExclusiveLock attempts to acquire an Exclusive lock on the touch file.

func (*TouchFile) Lock

func (tf *TouchFile) Lock(ctx context.Context, lockType LockType) error

Lock attempts to acquire the file lock. It will keep attempting to acquire the lock until the provided context times out or is canceled. If the lock cannot be acquired within the context's deadline, an error is returned.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

if err := tf.Lock(ctx, touchfile.Shared); err != nil {
    log.Fatalf("failed to acquire shared lock: %v", err)
}
defer func() {
    if err := tf.Unlock(); err != nil {
        log.Printf("failed to release lock: %v", err)
    }
}()

func (*TouchFile) Path

func (tf *TouchFile) Path() string

Path returns the path to the touch file.

func (*TouchFile) SharedLock

func (tf *TouchFile) SharedLock(ctx context.Context) error

SharedLock attempts to acquire a Shared lock on the touch file.

func (*TouchFile) Unlock

func (tf *TouchFile) Unlock() error

Unlock releases the lock on the touch file. If an error occurs during unlocking, it is returned to the caller.

Example:

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := tf.Lock(ctx); err != nil {
    log.Fatalf("failed to acquire lock: %v", err)
}

if err := tf.Unlock(); err != nil {
    log.Printf("failed to release lock: %v", err)
}

func (*TouchFile) WithLock

func (tf *TouchFile) WithLock(ctx context.Context, lockType LockType, f func() error) error

WithLock is a convenience function that locks the touch file, executes the provided function, and then unlocks the touch file. If the lock cannot be acquired, the function will return an error. The lock is always released after the function is executed, even if the function returns an error.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

tf, err := touchfile.NewTouchFile("/tmp/mylockfile")
if err != nil {
    log.Fatalf("failed to create touch file: %v", err)
}

err = tf.WithLock(ctx, func() error {
    fmt.Println("Doing some work while holding the lock")
    return nil
})
if err != nil {
    log.Fatalf("operation failed: %v", err)
}

Jump to

Keyboard shortcuts

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