NAME

MooX::Role::RunAlone - prevent multiple instances of a script from running

VERSION

Version v0.0.0_01

SYNOPSIS

# normal mode
package My::Script;
 
use strict;
use warnings;
 
use Moo;
with 'MooX::Role::RunAlone';
 
...
 
__END__ # or __DATA__
 

# deferred mode
package My::DeferedScript;
 
BEGIN {
   $ENV{RUNALONE_DEFER_LOCK} = 1;
}
 
use strict;
use warnings;
 
use Moo;
with 'MooX::Role::RunAlone';
 
...

# exit immediately 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 that uses Moo 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 exlusive lock on the sctript'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 a void return 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 usually be suppressed during normal startup:

"FATAL: No __DATA__ or __END__ tag found"
"FATAL: A copy of '$0' is already running"

Note: this can be suppressed in deferred locking mode. See the noexit argument to runalone_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 immulate 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(
   attemtps => 3,
   interval => 2,
   verbose  => 1,
);
 
# basic call with retries enabled, but silent
my $locked = __PACKAGE__->runalone_lock(
   attemtps => 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)

Controls whetern the method will call exit( 1 ) or return a Boolean false upon failure. Settin it true allows the composing script to take additional/different actions.

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)

Set 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 if attempts is greater than one.

verbose (Boolean, default: 0)

Enables progress messages on STDERR if set. The following messages can appear:

"attemting to lock <data pkg> ... failed. Retrying <N> more time(s)"
"attemting to lock <data pkg> ... 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]

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, therefor 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 cronjobs 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 Elizibeth Mattijsen (ELIZABETH) in Sys::RonAlone. 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=MooX-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 MooX::Role::RunAlone

You can also look for information at:

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)