NAME
Role::RunAlone - prevent multiple instances of a script from running
VERSION
Version v0.0.0_03
SYNOPSIS
There are many diffrent ways that a script might be crafted to compose this role. The following is just the obvious ways that the author has thought of. The examples below are limited to help prevent boredom.
- normal mode, regular script
-
#!/usr/bin/perl use strict; use warnings; use Role::Tiny::With; with 'Role::RunAlone'; ... __END__ # or __DATA__
- normal mode, modulino
-
#!/usr/bin/perl package My::Script; use strict; use warnings; use Moo; with 'Role::RunAlone'; ... __END__ # or __DATA__
- deferred mode, regular script
-
#!/usr/bin/perl package My::DeferredScript; use strict; use warnings; BEGIN { $ENV{RUNALONE_DEFER_LOCK} = 1; } use Role::Tiny::With; with 'Role::RunAlone'; ... # exit if we are not alone __PACKAGE__->runalone_lock; # do work ... __END__ # or __DATA__
- deferred mode, modulino
-
#!/usr/bin/perl package My::DeferredScript; use strict; use warnings; BEGIN { $ENV{RUNALONE_DEFER_LOCK} = 1; } use Moo; with 'Role::RunAlone'; ... # exit if we are not alone __PACKAGE__->runalone_lock; # do work ... __END__ # or __DATA__
DESCRIPTION
This Role provides a simple way for a command line script to ensure that only a single instance of said script is able to run at one time. This is accomplished by trying to obtain an exclusive lock on the script's __DATA__
or __END__
section.
The Role will send a message to STDERR
indicating a fatal error and then call exit(2)
if neither of those tags are present. This behavior can not be disabled and occurs when the Role is composed.
Normal Locking
If one of the aforementioned tags are present, an attempt is made (via runalone_lock()
) to obtain an exclusive lock on the tag's file handle using flock
with the LOCK_EX
and LOCK_NB
flags set. A failure to obtain an exclusive lock means that another instance of the composing script is already executing. A message will be sent to STDERR
indicating a fatal condition and the Role will call exit(1)
.
The Role does nothing if the call to flock
is successful.
Deferred Locking
The composing script can tell the Role that it should not immediately call runalone_lock()
but should defer this action to the script. This is done like this:
BEGIN {
$ENV{RUNALONE_DEFER_LOCK} = 1;
}
The Role will return immediately after checking to see whether or not one of the tags are present instead of trying to get the lock.
Note: It is the responsibility of the composing script to call runalone_lock()
at an appropriate time.
Fatal Messages
There are two messages that are sent to STDERR
that cannot be suppressed during normal startup:
- "FATAL: No __DATA__ or __END__ tag found"
- "FATAL: A copy of '$0' is already running"
-
Note: this message can be suppressed in deferred locking mode. See the
noexit
argument torunalone_lock
.
METHODS
Only one method is currently exposed, but it is the workhorse when deferred mode is used.
runalone_lock
This method attempts to get an exclusive lock on the __END__
or __DATA__
handle that was located during the Role's startup. A composing script may emulate normal operation by simply calling this method with no arguments at the desired time. It will either return a Boolean true
if successful, or call exit
with a status code of 1
upon failure.
The method's behavior can be modified by four arguments. This allows the composing script to enable lock retries or perform custom operations as needed. (Note: the method is implemented as a class method
and may be called with either a class name or a composing object.
Examples:
# basic call with retries and progress messages enabled
my $locked = __PACKAGE__->runalone_lock(
attempts => 3,
interval => 2,
verbose => 1,
);
# basic call with retries enabled, but silent
my $locked = __PACKAGE__->runalone_lock(
attempts => 3,
interval => 2,
);
# make a single (silent) attempt, but return to the caller instead of
# exiting if the attempt fails. also suppresses any failure message.
my $locked = __PACKAGE__->runalone_lock(
noexit => 1,
);
Arguments
Invalid values will cause an exception to be thrown via croak
so the offending caller might be more easily identified.
- noexit (Boolean, default: 0)
-
If
false
, the method will callexit(1)
if the call toflock
fails. Setting ittrue
will cause the method to return the result of the call toflock
.Note: if set, it will also suppress the fatal error message associated with failure to obtain a lock.
- attempts (Integer, must satisfy 0 < N < 10; default: 1)
-
Sets how many attempts will be made to get a lock on the handle in question.
- interval (Integer, must satisfy 0 < N < 10, default: 1)
-
Sets how long to
sleep
between attempts ifattempts
is greater than one. - verbose (Boolean, default: 0)
-
Enables progress messages on STDERR if set. The following messages can appear: ("pkg" will be replaced by the namespace the tag is in.)
"Attempting to lock pkg::DATA ... Failed, retrying <N> more time(s)" "Attempting to lock pkg::DATA ... SUCCESS"
Returns
1
if the lock was obtained.
The method will either call exit(1)
or return a Boolean false
depending upon the value of the noexit
argument.
PRIVATE METHODS
There are a few internal methods that are not documented here. All such methods begin with the string _runalone_
in an attempt to avoid namespace collision.
CAVEATS
[NB: This section has been copied from Sys::RunAlone
]
Symlinks
Execution of scripts that are (sym)linked to another script, will all be seen as execution of the same script, even though the error message will only show the specified script name. This could be considered a bug or a feature.
Changing a Running Script
If you change the script while it is running, the script will effectively lose its lock on the file. causing any subsequent run of the same script to be successful, therefore causing two instances of the same script to run at the same time (which is what you wanted to prevent by using Sys::RunAlone in the first place). Therefore, make sure that no instances of the script are running (and won't be started by cron jobs while making changes) if you really want to be 100% sure that only one instance of the script is running at the same time.
ACKNOWLEDGMENTS
This Role relies upon a principle that was first proposed (so far as this author knows) by Randal L. Schwartz MERLYN
, and first implemented by Elizabeth Mattijsen ELIZABETH
in Sys::RunAlone (currently maintained by Ben Tilly TILLY
.) That module has been extended by PERLANCAR
in Sys::RunAlone::Flexible with suggestions by this author.
SEE ALSO
Sys::RunAlone, Sys::RunAlone::Flexible
AUTHOR
Jim Bacon, <boftx at cpan.org>
BUGS
Please report any bugs or feature requests to bug-moox-role-runalone at rt.cpan.org
, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Role-RunAlone. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Role::RunAlone
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
LICENSE AND COPYRIGHT
This software is Copyright (c) 2020 by Jim Bacon.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.