NAME

IPC::Semaphore::SmokeSignals - A mutex and an LRU from crack pipe technology

SYNOPSIS

use IPC::Semaphore::SmokeSignals qw< LightUp >;

my $pipe = LightUp();

sub threadSafe
{
    my $puff = $pipe->Puff();
    # Only one thread will run this code at a time!
    ...
}

DESCRIPTION

A friend couldn't get APR::ThreadMutex to work so I offered to roll my own mutual exclusion code when, *bong*, I realized this would be trivial to do with a simple pipe.

It is easiest to use as a very simple mutex (see Synopsis above).

You can also use this as a semaphore on a relatively small number of relatively small tokins (each tokin' must be the same number of bytes and the total number of bytes should be less than your pipe's capacity or else you're in for a bad trip).

It also happens to give out tokins in LRU order (least recently used).

To use it as a semaphore / LRU:

my $bong = LightUp( 12 );
my @pool;

sub sharesResource
{
    my $dragon = $bong->Puff();
    # Only 12 threads at once can run this code!

    my $puff = $dragon->Sniff();
    # $puff is '01'..'12' and is unique among the threads here now

    Do_exclusive_stuff_with( $pool[$puff-1] );
    if(  ...  ) {
        $dragon->Exhale();  # Return our tokin' prematurely
        die ExpensivePostMortem();
    }
}

sub stowParaphernalia
{
    # Calling all magic dragons; waiting for them to exhale:
    $bong->Extinguish();
    ...
}

EXPORTS

There are 3 functions that you can request to be exported into your package. They serve to prevent you from having to type the rather long module name (IPC::Semaphore::SmokeSignals) more than once.

LightUp

LightUp() activates a new pipe for coordinating that only $N things can happen at once.

use IPC::Semaphore::SmokeSignals 'LightUp';

$pipe = LightUp( $fuel, $path, $perm );

To use an un-named pipe (such as if you are about to spawn some children):

my $pipe = LightUp();
# same as:
my $pipe = LightUp(1);

my $pipe = LightUp(50);
# same as:
my $pipe = LightUp(['01'..'50']);

This has the advantages of requiring no clean-up and having no chance of colliding identifiers (unlike with SysV semaphores).

It is often better to use MeetUp() if using a named pipe (FIFO), but it is possible to use a named pipe via:

my $pipe = LightUp( 8, "/var/run/my-app.pipe" );
# same as:
my $pipe = LightUp( 8, "/var/run/my-app.pipe", 0666 );
# same as:
my $pipe = LightUp( [1..8], "/var/run/my-app.pipe", 0666 );

LightUp(...) is just short for:

IPC::Semaphore::SmokeSignals->Ignite(...);

The first argument, $fuel, if given, should be one of:

A false value

This is the same as passing in a '1'.

An array reference

The array should contain 1 or more strings, all having the same length (in bytes).

A positive integer

Passing in $N gives you $N tokins each of length length($N). So 12 is the same as ['01'..'12'].

The second argument, $path, if given, should give the path to a FIFO (or to where a FIFO should be created). If $path is not given or is a false value, then Perl's pipe() function is called to create a non-named pipe.

The third argument, $perm, if given, overrides the default permissions (0666) to use if a new FIFO is created. Your umask will be applied (by the OS) to get the permissions actually used.

Having a second process LightUp() the same $path after another process has lit it up and while any process is still using it leads to problems. The module does not protect you from making that mistake. This is why it is usually better to use MeetUp() when wanting to use a FIFO.

JoinUp

JoinUp() connects to an existing named pipe (FIFO):

use IPC::Semaphore::SmokeSignals 'JoinUp';

$pipe = JoinUp( $bytes, $path );

JoinUp(...) is just short for:

IPC::Semaphore::SmokeSignals->JoinIn(...);

The $bytes argument must be the number of bytes of each tokin' used when the FIFO was created [by LightUp() or by MeetUp()].

The FIFO must already exist (at $path). The call to JoinUp() can block waiting for the creator to connect to the FIFO.

MeetUp

MeetUp() coordinates several unrelated processes connecting to (and maybe creating) a named pipe (FIFO), ensuring that only one of them initializes it.

use IPC::Semaphore::SmokeSignals 'MeetUp';

$pipe = MeetUp( $fuel, $path, $perm );

MeetUp(...) is just short for:

IPC::Semaphore::SmokeSignals->Meet(...);

The $fuel and $path arguments are identical to those same arguments for LightUp().

It is often best to omit the $perm argument (or pass in a false value), which will cause MeetUp() to fail if the FIFO, $path, does not yet exist. This is because deleting the FIFO makes it possible for there to be a race during initialization.

If you pass in a true value for $perm, likely 0666, then the FIFO will be created if needed (but this will also trigger a warning).

METHODS

Ignite

my $pipe = IPC::Semaphore::SmokeSignals->Ignite( $fuel, $path, $perm );

See LightUp.

JoinIn

my $pipe = IPC::Semaphore::SmokeSignals->JoinIn( $bytes, $path );

See JoinUp.

Meet

my $pipe = IPC::Semaphore::SmokeSignals->Meet( $fuel, $path, $perm );

See MeetUp.

Puff

my $dragon = $pipe->Puff();

my $dragon = $pipe->Puff('impatient');

Puff() takes a drag on your pipe and stores the tokin' it gets in a magic dragon that it gives to you. Store the dragon in a lexical variable so that when you leave the scope of that variable, the tokin' will automatically be returned to the pipe (when the variable holding the dragon is destroyed), making that tokin' available to some other pipe user.

The usual case is to use a semaphore to protect a block of code from being run by too many processes (or threads) at the same time.

If you need to keep your tokin' reserved beyond any lexical scope containing your call to Puff(), then you can pass the dragon around, even making copies of it. When the last copy is destroyed, the tokin' will be returned. Or you can release it early by calling Exhale() on it.

If there are no available tokins, then the call to Puff() will block, waiting for a tokin' to become available. Alternately, you can pass in a true value as the only argument to Puff() and this will cause Puff() to return immediately, either returning a magic dragon containing a tokin' or just returning a false value.

For example:

{
    my $dragon = $pipe->Puff('impatient');
    if( ! $dragon ) {
        warn "Can't do that right now.\n";
    } else {
        # This code must never run more than $N times at once:
        ...
    }
}

If the initializer of the pipe has called Extinguish() on the pipe, then any calls to Puff() (in any process) can die with the message:

"The pipe is going out.\n"

Or, you can call Puff() in a list context so that, instead of die()ing, it will just return 0 items. For example:

{
    my( $dragon ) = $pipe->Puff('impatient')
        or  return 'done';
    if( ! $dragon ) {
        warn "Can't do that right now.\n";
    } else {
        # This code must never run more than $N times at once:
        ...
    }
}

The or return is only run if $pipe has been Extinguish()ed. For example, when Puff() returns an undef, the list assignment will return the number of values assigned (1), which is a true value, preventing the or return from running.

Or you can just not worry about this by not calling Extinguish().

Sniff

my $tokin = $dragon->Sniff();

Calling Sniff() on a magic dragon returned from Puff() will let you see the value of the tokin' that you have reserved.

Calling Sniff() on a magic dragon that has already had Exhale() called on it will return undef.

Exhale

$dragon->Exhale();

Calling Exhale() on a magic dragon returned from Puff() causes the dragon to release the reserved tokin' immediately.

This can also be done by just overwriting the dragon, for example:

$dragon = undef;

but only if $dragon is the last/only existing copy of the dragon.

Extinguish

$pipe->Extinguish();

my $leftovers = $pipe->Extinguish( 'impatient' );

Extinguish() marks the pipe as being shut down and starts pulling out and discarding all of the tokins in it. But it is a no-op (and returns undef) if the caller was not the process that lit the pipe.

If you pass it a true value, then it will still remove tokins from the pipe as fast as possible, but it will not hang waiting for any outstanding tokin' to be returned to the pipe. In such a case, the number of outstanding tokins is returned.

In all cases, 0 is returned if the call managed to completely shut down the pipe (always the case if no true value was passed in).

NAMED PIPES

If you need a semaphore (or LRU) between unrelated processes or just processes where it is inconvenient to create the SmokeSignals object in their parent process, then you can use a named pipe (a FIFO):

use IPC::Semaphore::SmokeSignals qw< MeetUp >;

my $pipe = MeetUp( 12, "/var/run/mp-app" );

Please don't add code to delete the FIFO when you think you are done with it. That can just lead to races in initialization.

Or, if you want to designate one process as being responsible for setting up the paraphernalia:

use IPC::Semaphore::SmokeSignals qw< LightUp JoinUp >;

my $path = "/var/run/my-app.pipe";
my $pipe =
    $is_owner
        ? LightUp( 12, $path, 0666 )
        : JoinUp( length('12'), $path );

In this case, calls to JoinUp() will block, waiting for the owner to at least put the pipe in place (but not waiting until the pipe is fully lit). Note that this can thwart our checking to make sure that the total size of all of the tokins does not exceed your pipe's capacity (which could later lead to deadlock).

PLANS

A future version may allow for setting a maximum wait time.

CONTRIBUTORS

Author: Tye McQueen, http://perlmonks.org/?node=tye