RevBank::FileIO
Line-based text file manipulation with advisory locking
Synopsis
with_lock {
...
};
my $data = slurp $filename;
my @lines = slurp $filename;
spurt $filename, @data;
append $filename, @data;
rewrite $filename, sub($line) {
return $line; # return changed or unchanged line
return undef; # exclude line from file
};
Description
This package implements very simple locking to protect against filesystem based race conditions when running multiple instances of revbank on the same data files.
These race conditions are probably exceptionally rare and very hard to trigger in real-world conditions, because file system access is very fast due to caching and buffering by the kernel. RevBank was used for over a decade without any known problem due to such a race condition, before locking was finally added.
No attempt was made to optimize for performance, and all locks are global and exclusive.
Waits for the global lock for about two minutes, printing a message every few seconds to keep the user informed, and then dies.
Functions
File names are relative to REVBANK_DATADIR.
with_lock BLOCK
Gets the lock, executes the block, releases the lock again. Returns whatever the block returned.
Use this instead of get_lock to prevent forgetting to release the lock.
When REVBANK_SKIP_LOCK is true, runs the block without the lock. This is
useful if a synchronous parent process already holds the lock, and is dangerous
in every other case.
get_lock
Acquires the lock if it is not already held. Keeps extra virtual locks (by virtue of a simple counter) if the global lock is already held by the current process.
Calling this function directly is discouraged. Use with_lock instead.
release_lock
Decreases the number of virtual locks, releasing the real lock if none are left.
Calling this function directly is discouraged. Use with_lock instead.
slurp($filename)
Returns the entire contents of the file. In list context, returns a list of lines (including the line ending).
peek($filename)
Like slurp, but doesn't wait for the lock, and returns nothing (undef in
scalar context) if the file can't be read. For reads that may be stale or
missing, like those for completion. A file that is being appended to may be
read with its last line incomplete.
spurt($filename, @data), append($filename, @data)
Writes to a file. No separators or delimiters are added to the provided data, so in general you will want to pass either the entire contents as a single string, or a list of lines that already have line endings.
rewrite($filename, sub($line) { ...; return $line; })
Rewrites the existing text file. The provided subroutine is called for each line, and must return everything that should be written back. The sub can return undef to essentially skip (remove) a line.
mtime($filename)
Returns the modification time of the file (with fractional seconds if supported).
Caveats
- A lock file is used, so external processes should not depend on the individual files being flocked.
- Using a text editor while revbank is running and changing files will still mess things up.
- The locking mechanism provides a lock per process; different parts (e.g. plugins) of the same process can still simultaneously do things, so keep to the pattern of always closing files before returning control or forking.
Author
Juerd Waalboer #####@juerd.nl