NAME

cPanel::StateFile - Standardize the handling of file-based state data.

VERSION

This document describes cPanel::StateFile 0.605

SYNOPSIS

use cPanel::StateFile;

# create some cacheable object $obj.
# ...

my $state = cPanel::StateFile->new(
    { state_file => '/path/to/state/file', data_obj => $obj }
);
$state->synch(); # now memory and disk match.

# Prepare to make a change to object.

{
    my $guard = $state->synch();
    # Cache is now locked against changes
    # make changes to $obj
    $obj->modify();

    # update state
    $guard->update_file();
}
# state matches memory and file is unlocked.

DESCRIPTION

We have a generic need to be able to safely state data in a disk file. Unfortunately, we've redeveloped this multiple times in slightly different ways. I needed yet another implementation, so I decided to do it more generically.

The safety is provided by a pair of cooperating classes: cPanel::StateFile and cPanel::StateFile:Guard. The Guard object provides safe locking and makes certain that the lock is always released in the face of exceptions and such.

The Guard object is returned by the cPanel::StateFile::synch() method that reads the disk data into the memory object. If you don't store this object, the lock is immediately released. If you hold the Guard object, you can write to the disk using the Guard::update_file() method.

INTERFACE

The interface for this system is described in two parts: cPanel::StateFile and cPanel::StateFile::Guard.

cPanel::StateFile

The cPanel::StateFile interface creates the state file and provides a way to lock and read the file. For methods to modify the state on disk, see the next section on cPanel::StateFile::Guard.

cPanel::StateFile->new( $hashref )

Create a new cPanel::StateFile object. The only parameter is a hashref that contains the following parameters:

cache_file

Deprecated option that is now replaced by state_file.

state_file

This parameter is required. The path to the state file that we use for this data.

data_obj

This parameter is required. The data object that will be cached in the file. This object must support two methods for dealing with the state file.

locker

This optional parameter supplies a file locking object to replace the default declared for the StateFile class. See the section on "FILE LOCKER OBJECT" for the interface this class must provide.

If this object is not provided, it will default to the class-level object supplied on import or an object of the default cPanel::StateFile::FileLocker class. This class is only used if no object is provided.

logger

This optional parameter supplies a logging object to replace the default declared for the StateFile class. See the section on "LOGGER OBJECT" for the interface this class must provide.

If this object is not provided, it will default to the class-level object supplied on import or an object of the DefaultLogger class.

$obj->load_from_cache( $fh )

Will receive an opened file handle to read the data from.

This method is not responsible for opening or closing the filehandle. This method will only be called when the StateFile object determines that the data needs to be re-read, so you do not need to try to figure that out yourself.

The method should throw an exception on failure.

$obj->save_to_cache( $fh )

Will receive an opened file handle pointing to the truncated file on which to write the data.

This method is not responsible for opening or closing the filehandle. This method will only be called when the StateFile object determines that the data needs to be saved, so you do not need to figure that out in this method.

The method should throw an exception on failure.

timeout

This optional parameter specifies the timeout in seconds for flocking the file on open. The default value is 60 seconds.

$state->synch()

This method is called to make sure that the memory version of the data matches the disk version. If the state file doesn't exist, this method makes certain the Guard::update_file() is called to create the initial version.

If the disk file is newer than the version in memory, the data object is given a chance to load itself. THe file is locked for the duration on the read and the lifetime of the returned Guard object. If the Guard object is not stored, the file is immediately closed and unlocked.

$cache->get_logger()

Return the logger object used by the StateFile object. See the section on "LOGGER OBJECT" for the interface this object provides.

$state->throw( $msg )

Log the supplied message and die.

$state->warn( $msg )

Log the supplied message as a warning.

$state->info( $msg )

Log the supplied message as an informational message.

cPanel::StateFile::Guard

The cPanel::StateFile::Guard interface provides the writing methods and makes the locking safe. Once you have a guard, there is no reason to load data from the disk file, the in-memory copy matches the disk copy.

$guard->update_file()

Overwrite the state file from the memory object. This destroys whatever was in the file on disk and replaces it with what is in memory.

This method performs its work using the save_to_file method of the data object associated with the StateFile object.

$guard->call_unlocked( $coderef )

Sometimes, it is necessary to call a long-running process while you have the queue locked. You don't want to leave the queue locked, so that other processes can access it. But several blocks of locking and unlocking code can quickly result in race conditions.

This method solves that problem. It is called with a code reference. The queue lock is temporarily released, we run the code, and then the queue lock is reacquired. This allows one lock to protect code that works on the queue, without blocking on other code forever.

LOGGER OBJECT

By default, the StateFile uses die and warn for all messages during runtime. However, it supports a mechanism that allows you to insert a logging/reporting system in place by providing an object to do the logging for us.

To provide a different method of logging/reporting, supply an object to do the logging as follows when useing the module.

use cPanel::StateFile ( '-logger' => $logger );

The supplied object should supply (at least) 4 methods: throw, warn, info, and notify. When needed, these methods will be called with the messages to be logged.

For example, an appropriate class for Log::Log4perl and Email::Sender might do something like the following:

package Policy::Log4perl;
use strict;
use warnings;
use Log::Log4perl;
use Email::Sender::Simple;
use Email::Simple;
use Email::Simple::Creator;

sub new {
    my ($class) = shift;
    my $self = {
        logger => Log::Log4perl->get_logger( @_ )
    };
    return bless, $class;
}

sub throw {
    my $self = shift;
    $self->{logger}->error( @_ );
    die @_;
}

sub warn {
    my $self = shift;
    $self->{logger}->warn( @_ );
}

sub info {
    my $self = shift;
    $self->{logger}->info( @_ );
}

sub notify {
    my $self = shift;
    my $subj = shift;
    my $email = Email::Simple->create(
        header => [
            From => 'taskqueue@example.com',
            To => 'sysadmin@example.com',
            Subject => $subj,
        ],
        body => shift,
    );
    Email::Sender::Simple::sendmail( $email );
}

This would call the Log4perl code as errors or other messages result in messages.

This only works once for a given program, so you can't reset the policy in multiple modules and expect it to work.

In addition to setting a global logger, a new logger object can be supplied when creating a specific StateFile object.

throw( $msg )

This method is expected to log or report the critical error condition supplied as an argument and then use die to exit the method.

warn( $msg )

This method is expected to log or report a warning condition using the supplied message and then return.

info( $msg )

This method is expected to optionally report or log the supplied informational message and then return.

notify( $subj, $msg )

This method is expected to perform some form of critical notification of the triggering condition (such as an email to an appropriate administrator). The first argument is a summary or title and the second is the body of the supplied message.

The method should return on completion.

FILE LOCKER OBJECT

The default file locking policy is build around the flock function. However, this method may not be appropriate in all circumstances. So StateFile supports the ability to replace the locking policy by supplying a file locking object.

To provide a different method of file locking, use the following syntax when useing the module.

use cPanel::StateFile ( '-filelock' => $locker );

The supplied object should supply (at least) 2 methods: file_lock, and file_unlock.

file_lock( $file )

To the class name, this method receives the name of the file to protect with the lock. This is not the name of a lock file. The method should return a token that is passed to the file_unlock method to tell which lock to unlock.

If the method coannot protect (or lock) the file, the method should throw an exception.

file_unlock( $lock )

To the class name, this method receives the return value from the file_lock method to use when unprotecting the file.

DEPENDENCIES

Fcntl and File::Path.

BUGS AND LIMITATIONS

At present, the synch system detects changes in the file by means of the file's modification time and size. If a file is modified within the resolution of the mtime and it's size does not change, it may not be detected.

COPYRIGHT

Copyright (c) 2013, cPanel, Inc. All rights resrved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.