NAME
IPC::LockTicket - Use Storable library to IPC token to prevent parallel access to any resources. Including your custom data to transfer asynchronously.
SYNOPSIS
use strict;
use warnings;
use IPC::LockTicket;
my $object = IPC::LockTicket->New(@options);
my $object = IPC::LockTicket->New(qq{name}, 0666);
# ...or
my $object = IPC::LockTicket->New(qq{/absolute/path.file}, 0600)
# This fails if the IPC file exists already
$bol_succcess = $object->MainLock();
$bol_succcess = $object->SetCustomData($reference);
# fork() and do within Child1:
# Child1 represents best practice.
$bol_succcess = $object->TokenLock(); # Blocks unless lock is aquired.
$reference = $object->GetCustomData();
# Do something with $reference
$bol_succcess = $object->SetCustomData($reference);
$bol_succcess = $object->TokenUnlock(); # Other children can only use the
# updated version, but were blocked until this point. Best practice is
# keeping the Time between TokenLock() and TokenUnlock() as short as
# possible.
# Within a different Child2:
$reference = $object->GetCustomData(); # This is safe, but right after this
# call the data might change, because it is not controlled through any mechanism.
# This is useful if one just needs to get the current state, but doesn't rely on
# persistence.
# Within another different ChildN:
$bol_succcess = $object->SetCustomData($reference); # This is safe, too, but
# it might lead to data loss through overwriting existing data.
# At the end the parent does:
$bol_succcess = $object->MainUnlock();
# Hand over a true value if multiple parents shall use the same IPC file:
$bol_succcess = $object->MainLock(1);
DESCRIPTION
IPC::LockTicket allows one to get a simple token/ticket locking mechanism, like flock() does, but FIFO sorted. It also makes it easy to transport small amount of data between processes.
The data you want to transfer must be saved as an anonymous reference, and returns as such.
The data is not transferred in real time, but only on request. While you might store whole objects if you need the most recent of them, lock the store, load it, change it and store it again before you unlock it again.
In theory, you can store as much data as your disk can hold, but be aware: this will slow down the lock mechanism. Use multiple files in this case: One only holds data (full path), the other is just for locking (dynamic path).
IPC::LockTicket Class METHODS
New
my $object = IPC::LockTicket->new($str_Name, $oct_Permission) or die;
Creates a new IPC::LockTicket object, which is returned. Returns undef on failure. Expects one or two arguments. First the name, optionally secondly the permission as an octal number.
- name
-
String just matching
m{^[-_a-z0-9]+$}ifor dynamic naming or a full path to a file. Mandatory. - permission
-
Access rights that the lock file will have. For example, if just a collision protection shall be implemented, it might be the best to set it to 0666, so collisions can be prevented over the whole system for every user. Expects an octal number, like chmod(1). Defaults to
0600.
DESTROY
my $bol_Success = $object->DESTROY();
Destroys the object so it removes lock files or PIDs from lock files. Automatically called on undef($object), die, exit, and when $object leaves the scope.
Not called if another module croak()s or if the application fails through an exception at runtime.
If not called, the next MainLock() will clear the orphan file, but it is not clear that the content was written properly. If the file is empty or cannot be read, it will not be cleared, but MainLock() will fail. See MainLock for further details.
MainLock
my $bol_Success = $object->MainLock();
my $bol_Success = $object->MainLock(1);
Checks if a lock file exists and creates it if not. Fails if file exists and process stored within is alive. If a true value is supplied, locking is non-exclusive but shared. If the file exists and was also created non-exclusively, locking is successful. This way the lock file is shared with several processes, requesting non-exclusive mode. If false is returned from MainLock no lock file was created/claimed. Meaning:
____________________________________________________________________________________________________________________________________
| Call mode | MainLock() | MainLock() | MainLock() | MainLock() | MainLock(1) | MainLock(1) | MainLock(1) | MainLock(1) |
| Lock file | empty* | shared | exclusive | non-existent | empty* | shared | exclusive | non-existent |
| MainLock returns | false | false | false | true | false | true | false | true |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*= empty or any other non-Storable data.
MainUnlock
$object->MainUnlock();
Removes lock. The lock file is deleted if this is the last process accessing it. Returns only a true value.
If it is a shared lock and not the last process accessing it, only the PID entries are removed from the lock file.
TokenLock
$object->TokenLock();
Can only be called after MainLock and before MainUnlock otherwise dies. Requests exclusive lock, i.e., any TokenLock is blocking until TokenUnlock was called, or the locking process is dead. Process checks are done to prevent infinite locks through broken children or parents, but this is not proper usage. Don't just exit, but call TokenUnlock and MainUnlock in appropriate sequence before ending processes! Returns a true value or blocks.
TokenUnlock
$object->TokenUnlock();
Can only be called after MainLock and before MainUnlock otherwise dies. Removes the token from the lock file so any other request of TokenLock can be satisfied. Returns false if unlock is done while not holding the lock. Otherwise, it returns true.
SetCustomData
$object->TokenLock();
$object->SetCustomData($reference);
$object->TokenUnlock();
Writes a custom data reference in a reserved area. See CAVEATS and "GOOD PRACTICE" for further details. Should be preceded by TokenLock() and followed by TokenUnlock(). Returns only a true value or dies.
GetCustomData
$ref_Data = $object->GetCustomData();
Load the reference, formerly saved by SetCustomData. Returns undef either it was never set or an error occurred.
LOCKING
Locking works like a traffic light: Only if honoured, it can do its magic. flock() works the same way: only if all members honour the lock, it can be guaranteed to work properly. There are several methods implemented to prevent infinite locks through unexpectedly died processes. But the core of this is the token handling itself, providing a FIFO locking mechanism. The first process which called TokenLock is the first that will gain the lock. The last process, called TokenLock has to wait until the second to last called TokenUnlock. TokenLock is blocking. Until the former processes either call TokenUnlock or unexpectedly died.
CAVEATS
Using SetCustomData can lead to problems. For example, if a huge array is stored, the memory can exceed. However, even worse is: the more data is stored within, the slower the locking mechanism gets, because it has to write all the data every time it checks or changes the lock file. Refer to "GOOD PRACTICE" how to prevent this.
GOOD PRACTICE
Transferring huge amounts of data between processes
# Parent
my $obj_Lock = IPC::LockTicket->New('MyApp');
my $obj_IPC = IPC::LockTicket->New('/var/tmp/MyApp/storage.ipc');
$obj_Lock->MainLock(1);
$obj_IPC->MainLock(1);
...
# Child 1 has much data to transfer
$obj_Lock->TokenLock();
$obj_IPC->SetCustomData($referenceToHugeHashArray);
$obj_Lock->TokenUnlock();
...
# Child 2 shall work on the data
$obj_Lock->TokenLock();
my $referenceToHugeHashArray = $obj_IPC->GetCustomData();
$obj_IPC->SetCustomData(undef); # Clear storage to prevent working on the same elements several times...
$obj_Lock->TokenUnlock(); # ...and unlock fastly.
if ( defined($referenceToHugeHashArray) ) {
# Do something with the data...
exit(0);
}
else {
# Data was emptied or no data were saved yet.
exit(0);
}
Use dynamic naming for locking mechanisms only. This way, IPC::LockTicket tries to find the best location to store the file on its own while you keep the file small. For use of custom data transfer between processes, use full path notation and store on a slower, but huge partition. This way the lock mechanism can keep its speed, but you can transfer huge data as well. Maybe it is a better idea to use something like a database instead of a lock file for those situations. To consider this: the data is stored as a blob from Storable. This works well if you write once and clear the data afterwards. But it will re-write the whole file every time SetCustomData is called. This is inefficient if only a new hash pair shall be added and can make your app awfully slow. Better would be to prevent parallel database table access through IPC::LockTicket, so no redundant work happens, while databases are optimized to handle small amounts of data. Consider Child 2 reading the whole table, then truncating it. After that Child 1 might again write something new into the table.
Shared locks
It is possible to run MainLock(1) within the children, but this is bad practice and strongly discouraged! Shared locks shall only be shared between the main processes (parents) while children only use the TokenLock and TokenUnlock methods. The lock objects created through New('name') can be copied though forking. But New sets what is understood as the parent PID to the calling process. MainLock and MainUnlock expect to be run within the parent process. TokenLock is more optimized for speed than MainLock is.
AUTHOR
Dominik Bernhardt, domasprogrammer@gmail.com
CREDITS
Thanks for the hard time, much to learn and ideas I got through: