lfile

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Nov 24, 2020 License: MIT Imports: 5 Imported by: 1

README

go-lfile

A cross-platform Go package that provides a wrapper for os.File implementing RW mutex-like locking.

Transparently makes use of underlying system calls (flock/fcntl on Unix, LockFileEx on Windows) to provide locks on files, allowing file operations to synchronize within a process and between different processes.

Install

Add package:

go get github.com/JSBanya/go-lfile

Include:

include (
  // (...)
  "github.com/JSBanya/go-lfile"
  // (...)
)

Usage

Creating a LockableFile

The lfile.LockableFile struct is a composition of an *os.File, and thus implements all *os.File functions. Creating a new lfile.LockableFile is done by simply calling func New(file *os.File) *LockableFile:

Example:

f, err := os.OpenFile(tempFileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
  log.Fatal(err)
}

lf := lfile.New(f)
name := lf.Name() // Same as calling f.Name() as lfile.LockableFile is a composition of *os.File
Locking a file

To lock a lfile.LockableFile, simply call func (lf *LockableFile) Lock() error.

By default, locking a file is done in blocking mode. This is similar to a mutex in that execution will pause if the file is already locked elsewhere (i.e. by another process). Non-blocking mode can be enabled by calling func (lf *LockableFile) DisableBlocking(). In blocking is disabled, any calls to Lock that would normally pause execution (i.e. file is already locked) will instead return the error lfile.LOCK_CONFLICT. Blocking mode can be enabled again at any time by calling func (lf *LockableFile) EnableBlocking()

Two types of locks are provided: shared (read) locks, and exclusive (write) locks. Any number of processes may hold a read lock (shared lock) on a file, but only one process may hold a write (exclusive lock). Read locks are provided by calling func (lf *LockableFile) RLock() error and write locks are provided by calling func (lf *LockableFile) RWLock() error

Example (blocking mode):

// Open file as 'f' (not shown)

lf := lfile.New(f) // Default in blocking mode
if err := lf.RWLock(); err != nil { // Write lock (exclusive)
  log.Fatalf("Unable to lock file: %s", err)
}

// File is now locked and thread should have exclusive write access
lf.WriteString("my data")

Example (non-blocking mode):

// Open file as 'f' (not shown)

lf := lfile.New(f)
lf.DisableBlocking()

err := lf.RWLock() // Write lock (exclusive)
if err == lfile.LOCK_CONFLICT {
  // File is locked already
  // Do something else (e.g. sleep and try again)
} else if err != nil {
  log.Fatalf("Unable to lock file: %s", err)
}

// File is now locked and thread should have exclusive write access
lf.WriteString("my data")

Example (read lock):

// Open file as 'f' (not shown)
// Open same file as 'f2' (not shown)

lf := lfile.New(f)
if err := lf.RLock(); err != nil { // Read lock (shared)
  log.Fatalf("Unable to lock file: %s", err)
}

// (...)

lf2 := lfile.New(f2)
if err := lf2.RLock(); err != nil { // OK because previous lock was also a read (shared) lock
  log.Fatalf("Unable to lock file: %s", err)
}

// (...)

if err = lf.UnlockAndClose(); err != nil {
  log.Fatal(err)
}
 
if err = lf2.UnlockAndClose(); err != nil {
  log.Fatal(err)
}

Note: By default on Unix systems, lfile.LockableFile will use 'flock' to lock a file. For most purposes, this is likely desired, but 'fcntl' may instead be used by calling func (lf *LockableFile) UseFCNTL() (and can be switched back to flock via func (lf *LockableFile) UseFLOCK(); on windows, these function calls will have no effect). Note that 'fcntl' locks files on a process level and thus may not have expected behavior in a multithreaded environment. See https://gavv.github.io/articles/file-locks/ for more details on the difference between flock and fcntl.

Unlocking a file

Unlocking a file is done by simply calling func (lfile *LockableFile) Unlock() error. If the file is not locked or locked by a different process, this function call will have no effect.

Simultaneously unlocking and closing a file can by done in one function call: func (lf *LockableFile) UnlockAndClose() error

Example:

// Open file as 'f' (not shown)

lf := lfile.New(f)
if err := lf.RWLock(); err != nil {
  log.Fatalf("Unable to lock file: %s", err)
}

// Do stuff

if err := lf.Unlock(); err != nil {
  log.Fatalf("Unable to unlock file: %s", err)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	LOCK_CONFLICT = errors.New("File already locked")
)

Functions

This section is empty.

Types

type LockType

type LockType int
const (
	FLOCK LockType = 0 // Thread-safe if applied to different open() calls; May not allow NFS
	FCNTL LockType = 1 // Not thread-safe; Allows NFS; Max compatibility
)

type LockableFile

type LockableFile struct {
	*os.File // Composition; effectively acts as a standard os.File
	// contains filtered or unexported fields
}

func New

func New(file *os.File) *LockableFile

Converts an existing *os.File to a *LockableFile

func (*LockableFile) DisableBlocking

func (lf *LockableFile) DisableBlocking()

func (*LockableFile) EnableBlocking

func (lf *LockableFile) EnableBlocking()

func (*LockableFile) RLock

func (lf *LockableFile) RLock() error

func (*LockableFile) RWLock

func (lf *LockableFile) RWLock() error

func (*LockableFile) Unlock

func (lfile *LockableFile) Unlock() error

func (*LockableFile) UnlockAndClose

func (lf *LockableFile) UnlockAndClose() error

Unlocks any existing lock and calls file close

func (*LockableFile) UseFCNTL

func (lf *LockableFile) UseFCNTL()

func (*LockableFile) UseFLOCK

func (lf *LockableFile) UseFLOCK()

Jump to

Keyboard shortcuts

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