The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

IPC::ConcurrencyLimit::WithLatestStandby - IPC::ConcurrencyLimit with latest started working as standby

SYNOPSIS

  use IPC::ConcurrencyLimit::WithLatestStandby;

  sub run {
    my $limit = IPC::ConcurrencyLimit::WithLatestStandby->new(
      type              => 'Flock', # default, and currently only supported type
      path              => '/var/run/myapp',
    );

    if ($limit->get_lock) {
        # Got one of the worker locks (ie. number $id)
        do_work();
    } else {
        print "Failed to get a lock, replaced by a newer standby worker.\n";
    }
    # lock released with $limit going out of scope here
  }

  run();
  exit();

DESCRIPTION

This module behaves much the same as IPC::ConcurrencyLimit when configured for a single lock, with the exception of what happens when the lock is already held by another process. Instead of simply returning false, the lock will block and the worker will go into a "standby queue" waiting to acquire the master lock. If the master lock is released then the next worker in the queue takes over, and additionally workers in standby mode are managed such that older standby workers "bow out" when they detect a newer standby worker is available to take over waiting. When configured and used properly you are guaranteed to get the most recent worker "taking over" processing every time.

When using this module at any one time there may be up to four workers alive. One master process, one primary standby, one secondary standby, and one new standby, each one corresponding to one of four locks, numbered 0 to 3. Each new worker starts by acquiring the rightmost lock, #3, and then tries to "move left" by also acquiring the preceding lock, which when successful leads to it dropping its old lock. When the worker ends up holding the master lock #0 it can do work. At the same time the holder of the primary standby lock #1 also polls to see if the secondary standby lock #2 is held. If it is then the holder of the lock #1 exits, allowing the secondary standby lock to "move left" and take over standby responsibility.

So long as the polling time is sufficiently faster than the frequency with which new processes are started you will generally see the most recently started worker take over from the master. IOW, if you use the default poll time of 1 second and you start new workers minutely you can be confident that the new worker will be reasonably "fresh".

Options

IPC::ConcurrencyLimit::WithLatestStandby does not accept all the regular IPC::ConcurrencyLimit options. Currently it is restricted to using Flock internally, and max_procs may only be set to 1. There are also additional parameters which may be supplied.

poll_time
interval

This is the base amount of time that we should wait between checking if a lock is still held by another process. It is not the actual time that we may wait, which may be anything from 0 to 2 times the stated value, chosen randomly each time we sleep. Fractional seconds may be specified.

Note that new standby workers, and secondary standby workers poll at a faster rate than this value. The actual time is determined by $poll_time/$lock_id, meaning that a secondary standby worker polls twice as often as a primary standby worker. (For the lock algorithm to work properly we need to ensure that the poll times associated with each lock differ, and are not synchronized).

retries

Specify the maximum number of times we will attempt to get a lock. Note that due to the random sleep in our wait-and-retry loop this cannot be cleanly mapped to time. For that you can use the timeout setting, with or instead of the retries logic.

Addtionally one can provide a code reference to control the retry logic. The sub will be called with four arguments:

    my $should_retry= $retry_sub->($tries, $lock_tries, $elapsed, $lock_elapsed);

$tries is an integer which is incremented every time we try to acquire a lock internally, $lock_tries is similar but every time we successfully acquire a lock and "move left" it is reset to 0. $elapsed is the amount of time in secs (with fractional part) that has elapsed since we acquired the initial "new standby worker" lock, and $lock_elapsed is the amount of time since we last acquried a lock, with the timer being reset when we "move left". If the retry sub returns true then we retry, if it returns false then we exit out of the lock-wait loop.

Note that providing a reference for retries means that the timeout option is ignored.

timeout

Specify the maximum amount of time to wait for acquiring the master lock before exiting. This option may be combined with a non-reference retries value. See also retries for more details about finer grained control of the lock wait loop.

path

Specify the directory for Flock.

file_prefix

Specify a file prefix so that the files for this lock object can share a directory with other lock objects.

debug

Emit diagnostics when running. See debug_sub.

debug_sub

A sub to use for emitting diagnostics. Will be called with a single argument containing text to output.

process_name_change

Use this to tell the difference between active and standby workers in a process list. When this option is not disabled the $0 for the running process is updated to include the lock name that is currently held. This way you can see what state the worker is in by inspecting the process list using a tool like top or ps auwx.

When this option is enabled IPC::ConcurrencyLimit::WithLatestStandby will modify the running processes name via modification of $0 to show the state of the lock process and which lock is held if any. This is only supported on newer Perls and might not work on all operating systems. On my testing Linux, a process that showed as perl foo.pl in the process table before using this feature was shown as foo.pl - standby1 while in standby mode and as foo.pl - primary after getting the main worker lock.

Note this mode is slightly different from the same option in IPC::ConcurrencyLimite::WithStandby as it will NOT normally restore the previous value of $0 after exiting get_lock(). If you want that behavior set process_name_change to a value larger than 1.

Due to an oversight this parameter defaults to on or "enabled" in v0.15, and in later versions defaults to off or "disabled", you should explicitly set it if you wish to be sure of what will happen.

SEE ALSO

See also IPC::ConcurrencyLimit::WithStandby for a similar module but with different simpler semantics than this one.

AUTHOR

Yves Orton, yves@cpan.org

ACKNOWLEDGMENT

This module was originally developed for booking.com. With approval from booking.com, this module was generalized and put on CPAN, for which the authors would like to express their gratitude.

COPYRIGHT AND LICENSE

 (C) 2015 Yves Orton. All rights reserved.

 This code is available under the same license as Perl version
 5.8.1 or higher.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.