NAME

Solaris::ProcessContract - Manage Solaris process contracts via libcontract

VERSION

1.00

SYNOPSIS

use Solaris::ProcessContract qw(:flags);

my $pc = Solaris::ProcessContract->new();

# Get a template that we can use to control the contract of the next child
# process we create
my $template = $pc->get_template();

# Define how the new contract will behave
$template->set_parameters( CT_PR_PGRPONLY );

# Define which events to monitor in the new contract
$template->set_informative_events( CT_PR_EV_HWERR );
$template->set_fatal_events( CT_PR_EV_HWERR );
$template->set_critical_events( 0 );

# Activate this template, so that the next child process we create will use
# a brand new contract with the options in the template
$template->activate;

# Fork!
my $pid = fork();

# Child and parent processes
if ( $pid == 0 )
{
  # We are the child process, running in a newly created contract underneath
  # our parent's contract
  printf "child pid: %d\n", $$;

  # Clear the template from the context of the child process, so that any
  # children that this child happens to fork won't continue to create new
  # contracts and just use the one we already have
  $template->clear;

  # This is running in a different contract than the parent, hooray!
  exec( 'sleep', '999' );
}
else
{  
  # We are the parent process, still running in our original contract
  printf "parent pid: %d\n", $$;

  # Get the contract that was just created for our child process
  my $contract = $pc->get_latest_contract();

  # Show information about the contract the child is living in
  printf "child contract: %d\n", $contract->id();

  # Even though the child processes has its own contract, that new contract
  # is still underneath ours, so we need to abandon it so that it is no
  # longer related to us at all 
  $contract->control->abandon();

  # Clear the template from the context of the parent, so that any processes
  # we fork after this will return to the default behavior of using our
  # contract insted of creating their own
  $template->clear();
}

DESCRIPTION

This module allows you to use the libcontract(3) facility to manage contracts for your process and any child processes you create.

The process contract system on solaris allows you to group together related processes for monitoring and resource limiting.

By default, the child processes you create will live in the same contract group as the parent.

The solaris smf init system uses contracts to manage the processes of running services. This allows it do things like restart the service if the number of processes in the contract that the service lives in drops to zero.

This makes sense in most scenarios, like when your code is forking off copies of itself for parallelization, since a fault in one process probably means something is wrong with your entire service.

However, if your code is forking off worker processes that are unrelated to the parent process, having them all in the same contract is troublesome.

For example, if the parent process is a daemon that forks off unrelated worker processes, solaris will fail to detect when the number daemon processes has dropped to zero and the service is unavailable. Since the unrelated worker processes are in the same contract, those are counted against the "is the daemon still running?" checks.

To solve this, you need to create a new contract group for any worker processes you fork, so that they have their own space for monitoring and resource limitations and do not affect the parent process.

This is also a good practice for any code on solaris that forks off worker processes for long running or resource intensive jobs, as it allows for more accurate resource limits and better tracking of what resources are being used.

See:

man libcontract
man contract
man process
man smf
man svc.startd
man ctrun

METHODS

new

Arguments: \%opt

Returns: $pc

Returns a new instance of Solaris::ProcessContract.

my $pc = Solaris::ProcessContract->new();

The following options are available:

debug

Set this to true to enable debug mode, which will print information about which calls are being made under the hood to STDERR.

my $pc = Solaris::ProcessContract->new
(
  debug => 1,
);

get_template

Arguments: none

Returns: $template

Returns a new instance of Solaris::ProcessContract::Template that can be used to define how contracts will be behave the next time a child process is forked.

When the template is "activated", any processes forked afterwards will be created in a new contract with the options set in the template.

When the template is "cleared", it will no longer use the template and return the behavior of putting child processes in the same contract as the parent process.

# Get a fresh template
my $template = $pc->get_template();

# Set some options on the template
$template->set_parameters( CT_PR_INHERIT );

# Use this template to create a new contract the next time a process is
# forked
$template->activate();

# Stop using this template, go back to creating new processes in our the
# same contract we are running in
$template->clear();

Calls to the methods in "template" are context aware. For example, calling "clear" from inside the parent process will only change the behavior of the parent process. If the child process happens to fork of its own children, they will continue to create new contracts using the previously "activated" template.

To stop the child process from creating its own contracts, you will need to call "clear" in the context of the child process as well.

my $pid = fork();

if ( $pid == 0 )
{
  # We are the child, so let's not create any more contracts ourselves if
  # we fork our own children after this
  $template->clear;
}
else
{
  # We are the parent, let's also stop creating new contracts for any
  # children we fork after this
  $template->clear;
}

get_latest_contract

Arguments: none

Returns: $contract

Returns a new instance of Solaris::ProcessContract::Contract for the most recent contract that has been created by us.

This method is the most reliable way to get the contract for the last process you forked. Due to how solaris handles contracts, this will only ever return the latest contract that is within the scope of your process. This means you won't ever get a contract from an unrelated process on the system, and that it is safe to assume the latest contract is the one you just created when forking.

my $pid = fork();

if ( $pid == 0 )
{
  # Child
}
else
{
  # Get the contract that was just created for the child
  my $contract = $pc->get_latest_contract;
  print $contract->id();
}

get_latest_contract_id

Arguments: none

Returns: $id

Returns the id of the most recent contract that was created in the scope of your process.

get_contract

Arguments: $id

Returns: $contract

Returns a new instance of Solaris::ProcessContract::Contract for a specific contract id.

If you happen to know the id of the contract you want to monitor or control, you can just get it directly instead of using "get_latest_contract".

FLAGS

The flags used by libcontract(3) are available for use in this module.

They are not exported by default, so you will need to export them if you need to use them:

# Export all flags
use Solaris::ProcessContract qw(:flags);

# Export param flags only
use Solaris::ProcessContract qw(:param_flags);

# Export event flags only
use Solaris::ProcessContract qw(:event_flags);

If you still want to use the flags but don't want to import anything, you can still access them the hard way:

print Solaris::ProcessContract::CT_PR_NOORPHAN;
print Solaris::ProcessContract::CT_PR_EV_CORE;

Param Flags

The following flags can be used to control the behavior of a contract:

CT_PR_INHERIT

If set, allows this contract to be inherited if the contract above it dies.

Otherwise, it will be orphaned if the contract above it dies.

CT_PR_NOORPHAN

If set, don't allow orphaned contracts. If a contract is orphaned, kill all the processes in it.

Otherwise, the contract is allowed to be orphaned.

CT_PR_PGRPONLY

If set, only kill the processes that share the same process group when a fatal event happens.

Otherwise, all processes in the contract are killed when a fatal event happens.

CT_PR_REGENT

If set, allow this contract to inherit other contracts.

Otherwise, any contracts we attempt to inherit will stay abandoned.

CT_PR_ALLPARAM

Shortcut for setting all param flags.

Event Flags

The following flags can be used to control the events that will be monitored for a contract:

CT_PR_EV_EMPTY

The number of processes in this contract has dropped to zero.

CT_PR_EV_FORK

A new process has been forked in to this contract.

CT_PR_EV_EXIT

A process in this contract exited.

CT_PR_EV_CORE

A process in this contract dropped core.

CT_PR_EV_SIGNAL

A process in this contract received a fatal signal.

CT_PR_EV_HWERR

A process in this contract was killed due to system hardware error.

CT_PR_ALLEVENT

Shortcut for setting all event flags.

CT_PR_ALLFATAL

Shortcut for setting the "CT_PR_EV_CORE", "CT_PR_EV_SIGNAL" and "CT_PR_EV_HWERR" flags.

EXCEPTIONS

Exception handling is provided by the Solaris::ProcessContract::Exceptions class.

EXPORTS

This module exports nothing by default.

See "FLAGS" for a list of available exports.

TODO

Right now, this module gives you what you need to control the contract system when forking processes. That is going to be the most important need for most people writing daemon software in perl for solaris.

Ideally, it could be expanded to be able to fully monitor and acknowledge contract system events.

It also would be nice to have access to some of the deeper info when querying contracts, or maybe have a way to tie the proc filesystem in to the contract filesystem so that you could query both.

SEE ALSO

Solaris::ProcessContract::Template
Solaris::ProcessContract::Contract
Solaris::ProcessContract::Exceptions
libcontract(3)
contract(4)
process(4)

AUTHOR

Danny Warren

COPYRIGHT

Copyright (c) 2013, "AUTHOR".

LICENSE

This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.