revbank/lib/RevBank/FileIO.pod
2022-08-29 17:50:12 +02:00

96 lines
2.8 KiB
Text

=head1 NAME
RevBank::FileIO - Line-based text file manipulation with advisory locking
=head1 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
};
=head1 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 such a race condition, before locking was finally
added.
No attempt was made to optimize for performance, and all locks are global and
exclusive.
Will wait for the global lock for as long as it takes, printing a message every
few seconds to keep the user informed.
=head2 Functions
=head3 with_lock BLOCK
Gets the lock, executes the block, releases the lock again. Returns whatever
the block returned.
Use this instead of C<get_lock> to prevent forgetting to release the lock.
=head3 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 C<with_lock> instead.
=head3 release_lock
Decreases the number of virtual locks, releasing the real lock if none are
left.
Calling this function directly is discouraged. Use C<with_lock> instead.
=head1 slurp($filename)
Returns the entire contents of the file. In list context, returns a list of
lines (including the line ending).
=head1 spurt($filename, @data)
=head1 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.
=head1 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.
=head2 CAVEATS
=over 2
=item * A lock file is used, so external processes should not depend on the
individual files being flocked.
=item * Using a text editor while revbank is running and changing files will
still mess things up.
=item * 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.
=back