Role::RunAlone - prevent multiple instances of a script from running
Version v0.1.0
There are many diffrent ways that a script might be crafted to compose this role. The following is just some of the obvious ways that the author has thought of. The examples below are limited to help prevent boredom.
#!/usr/bin/perl use strict; use warnings; use Role::Tiny::With; with 'Role::RunAlone'; ... __END__ # or __DATA__
#!/usr/bin/perl package My::Script; use strict; use warnings; use Moo; with 'Role::RunAlone'; ... __END__ # or __DATA__
#!/usr/bin/perl 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__
#!/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__
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.
__DATA__
__END__
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.
STDERR
exit(2)
NOTE: The principle employed DOES NOT work if the script in question is being run on multiple machines. The locking mechanism only works when multiple instances are to be prevented on a single machine.
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).
runalone_lock()
flock
LOCK_EX
LOCK_NB
exit(1)
The Role does nothing if the call to flock is successful.
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.
There are two messages that are sent to STDERR that cannot be suppressed during normal startup:
Note: this message can be suppressed in deferred locking mode. See the noexit argument to runalone_lock.
noexit
runalone_lock
Only one method is currently exposed, but it is the workhorse when deferred mode is used.
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.
true
exit
1
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.
class method
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, );
Invalid values will cause an exception to be thrown via croak so the offending caller might be more easily identified.
croak
If false, the method will call exit(1) if the call to flock fails. Setting it true will cause the method to return the result of the call to flock.
false
Note: if set, it will also suppress the fatal error message associated with failure to obtain a lock.
Sets how many attempts will be made to get a lock on the handle in question.
Sets how long to sleep between attempts if attempts is greater than one.
sleep
attempts
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"
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.
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.
_runalone_
The principle employed DOES NOT work if the script in question is being run on multiple machines. The locking mechanism only works when multiple instances are to be prevented on a single machine.
[NB: This section has been copied from Sys::RunAlone]
Sys::RunAlone
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.
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.
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.
MERLYN
ELIZABETH
TILLY
PERLANCAR
Sys::RunAlone, Sys::RunAlone::Flexible
Jim Bacon, <boftx at cpan.org>
<boftx at cpan.org>
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.
bug-moox-role-runalone at rt.cpan.org
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)
https://rt.cpan.org/NoAuth/Bugs.html?Dist=Role-RunAlone
AnnoCPAN: Annotated CPAN documentation
http://annocpan.org/dist/Role-RunAlone
CPAN Ratings
https://cpanratings.perl.org/d/Role-RunAlone
Search CPAN
https://metacpan.org/release/Role-RunAlone
This software is Copyright (c) 2020 by Jim Bacon.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
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.
To install Role::RunAlone, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Role::RunAlone
CPAN shell
perl -MCPAN -e shell install Role::RunAlone
For more information on module installation, please visit the detailed CPAN module installation guide.