The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

EventServer - the all singing, all dancing server. Handles i/o driven clients, timer driven clients, and interrupt driven clients, all at the same time. Allows user defined events also. If there are any more types of clients, please tell me.

SYNOPSIS

Functions can be imported and used:

    use EventServer;
    
    $r1 = register_timed_client($obj1,$time1,$coderef1);
    $r2 = register_interval_client($obj2,$time2,$coderef2);
    $r3 = register_signal_client($obj3,$signal3,$coderef3);
    $r4 = register_io_client($obj4,$mode4,$coderef4_r,
                $coderef4_w,$coderef4_rw);
    $r5 = register_child_termination_client($obj5,$pid5,$coderef5);
    $r6 = register_event_client($obj6,$eventName6,$coderef6);
    
    trigger_on_deregistering($r1,$coderef7);
    cancel_registration($r3);
    $ordered_keys_ref = ordered_keys_ref();
    
    $time = maximum_inactive_server_time();
    set_maximum_inactive_server_time($time);
    execute_in_array_context_with_timeout($timeout,$timeout_retcode,
                $error_retcode,$coderef,@args);
    
    fork_with_child_retaining_clients($r1,$r2,...);
    
    sub something {add_event($eventName)}
    
    start_server();

Or the class can be used with methods:

    require EventServer;
    $S = EventServer;
    
    $r1 = $S->registerTimedClient($obj1,$time1,$coderef1);
    $r2 = $S->registerIntervalClient($obj2,$time2,$coderef2);
    $r3 = $S->registerSignalClient($obj3,$signal3,$coderef3);
    $r4 = $S->registerIOClient($obj4,$mode4,$coderef4_r,
                $coderef4_w,$coderef4_rw);
    $r5 = $S->registerChildTerminationClient($obj5,$pid5,$coderef5);
    $r6 = $S->registerEventClient($obj6,$eventName6,$coderef6);
    
    $S->triggerOnDeregistering($r1,$coderef7);
    $S->cancelRegistration($r3);
    $ordered_keys_ref = $S->orderedKeysRef();
    
    $time = $S->maximumInactiveServerTime();
    $S->setMaximumInactiveServerTime($time);
    $S->executeInArrayContextWithTimeout($timeout,$timeout_retcode,
                $error_retcode,$coderef,@args);
    
    $S->forkWithChildRetainingClients($r1,$r2,...);
    
    sub something {$S->addEvent($eventName)}
    
    $S->startServer();

IMPORTANT

The 'ALRM' signal is used extensively, as is alarm(). You should NOT call the function alarm() as this will corrupt the internal logic of the server. Similarly sleep() should not be used either, as this is often implemented in terms of alarm().

Instead use execute_in_array_context_with_timeout() which is better anyway since it allows multiple clients to set alarms simultaneously and allows nested alarms. However, for this reason, registering a client to receive 'ALRM' signals is probably of no use.

Also, if you assign to the %SIG hash, or install signal handlers through POSIX yourself, then you may corrupt the logic of the server. If you need to do this for something other than a signal (e.g. __WARN__), that should be okay, otherwise you should probably create a subclass to install the handlers you want (see "The SIG hash and signals" and "Creating Subclasses").

CONTENTS

"NAME"

"SYNOPSIS"

"IMPORTANT"

"CONTENTS"

"Function and Method Summary"

"Including the server in your program"

"Starting the server"

"Registering clients (general)"

"Registering clients (methods)"

"Client order for simultaneous events"

"Deregistering clients"

"Timeouts within client code"

"Forking child processes"

"Times and Timing"

"The SIG hash and signals"

"Example"

"Creating Subclasses"

"Example subclasses"

"Possible problems"

"Questions and Answers"

"AUTHOR"

"COPYRIGHT"

"MODIFICATION HISTORY"

Function and Method Summary

There are 15 public functions/methods:

8 dealing with registering clients;

1 to add user defined events

3 dealing with executing code and timeouts;

1 to fork the process;

and 1 to start the server.

Functions are:

 register_interval_client(O/R,INTERVAL,FUNCREF,ARG)
 register_timed_client(O/R,TIMEOUT,FUNCREF,ARG)
 register_io_client(O/R,MODE,HANDLE,RFUNCREF,WFUNCREF,RWFUNCREF,ARG)
 register_signal_client(O/R,SIGNAL,FUNCREF,ARG)
 register_child_termination_client(O/R,PID,FUNCREF,ARG)
 register_event_client(O/R,EVENT,FUNCREF,ARG)
 
 trigger_on_deregistering(REGISTRY_KEY,FUNCREF)
 cancel_registration(REGISTRY_KEY)
 
 add_event(EVENT)
 
 maximum_inactive_server_time()
 set_maximum_inactive_server_time(TIME)
 execute_in_array_context_with_timeout(TIMEOUT,TRET,ERET,FUNCREF,ARGS)
 
 fork_with_child_retaining_clients(LIST_OF_REGISTRY_KEYS)
 
 start_server();

And defined as methods:

 $SERVER->registerIntervalClient(O/R,INTERVAL,FUNCREF,ARG)
 $SERVER->registerTimedClient(O/R,TIMEOUT,FUNCREF,ARG)
 $SERVER->registerIOClient(O/R,MODE,HANDLE,RFUNCREF,WFUNCREF,RWFUNCREF,ARG)
 $SERVER->registerSignalClient(O/R,SIGNAL,FUNCREF,ARG)
 $SERVER->registerChildTerminationClient(O/R,PID,FUNCREF,ARG)
 $SERVER->registerEventClient(O/R,EVENT,FUNCREF,ARG)
 
 $SERVER->triggerOnDeregistering(REGISTRY_KEY,FUNCREF);
 $SERVER->cancelRegistration(REGISTRY_KEY);
 
 $SERVER->addEvent(EVENT)
 
 $SERVER->maximumInactiveServerTime()
 $SERVER->setMaximumInactiveServerTime(TIME)
 $SERVER->executeInArrayContextWithTimeout(TIMEOUT,TRET,ERET,FUNCREF,ARGS)
 
 $SERVER->forkWithChildRetainingClients(LIST_OF_REGISTRY_KEYS)
 
 $SERVER->startServer();

Including the server in your program

The server is included in your program with the line

 use EventServer;

to import the functions, or

 require EventServer;

if used as a class.

Starting the server

The server is started by executing the function or method

start_server();
EventServer->startServer();

In either case, if a subclass has been defined correctly, then the server will be started using that subclass.

Registering clients (general)

Clients are registered with the server using any of the 6 registering methods listed in the next section. They all have various points in common:

1. $SERVER is assumed to be EventServer or a subclass;

2. All registration methods return a RegistryKey object on success which holds the registration key, and false on failure. (Note previous versions returned a string - the current version should be fully compatible with previous versions). The registration key is unique to the registration, depending on all the parameters passed to the registration method - i.e a single object can be registered multiple times using different parameters or registration methods (multiple *identical* registrations will return the same key, and will result in only one registration). To alter the parameters of an existing registration, pass the registration key to the registration method instead of the object (see 'O/R' below). But note that this generates a new RegistryKey object since the registration parameters are now different (the old RegistryKey object is deregistered, and is essentially useless). Reregistering an existing registration so that it is identical to another registration will just derigister the first registration, returning the existing identical RegistryKey object (i.e. as stated above, there will only be one registry entry for identical parameters regardless of how you register them).

3. 'O/R' is the object being registered or the registration key of an already registered object. The object can be anything (previous versions restricted it to be class names or objects that returned true ref() values). This object is passed to FUNCREF (see below) as the first argument.

4. 'ARG' is anything. It is passed to FUNCREF (see below) as the last argument. If nothing is passed, then ARG is defaulted to undef();

5. At least one 'FUNCREF' argument is required. All FUNCREF arguments are CODE references to the function which is executed when the client is triggered. Where there is more than one FUNCREF to be specified, the one called will depend on the trigger type. When triggered, the FUNCREF is called as:

 &FUNCREF(OBJECT,REGISTRY_KEY,some method specific args,ARG);

where:

 OBJECT is the object registered (the 'O' in 'O/R' above);
 REGISTRY_KEY is the registration key for that registration
    (the 'R' in 'O/R' above, returned by registration methods);
 ARG is the last argument passed to the registration method
    ('ARG' above);

This call to FUNCREF takes place within a timeout. The current maximum timeout value can be retrieved using maximum_inactive_server_time(), and can be set using set_maximum_inactive_server_time(). (These access and set the global $EventServer::MAX_INACTIVE_SERVER_TIME.) The default value is 60 seconds. Any fatal errors caused by executing FUNCREF are trapped, and cause the client to be deregistered. A timeout will also cause the client to be deregistered.

NOTE however that a call to exit() cannot be trapped and will cause the server process to exit. Similarly, a call to dump() also cannot be trapped and will cause the server process to core dump.

Registering clients (methods)

register_interval_client (O/R,INTERVAL,FUNCREF,ARG)
$SERVER->registerIntervalClient(O/R,INTERVAL,FUNCREF,ARG)

INTERVAL is a time (see "Times and Timing"). The client is triggered after every INTERVAL seconds. Triggering effects the function call

 &FUNCREF(OBJECT,REGISTRY_KEY,INTERVAL,ARG);
register_timed_client (O/R,TIMEOUT,FUNCREF,ARG)
$SERVER->registerTimedClient(O/R,TIMEOUT,FUNCREF,ARG)

TIMEOUT is a time (see "Times and Timing"). The client is triggered after TIMEOUT seconds and then deregistered. Triggering effects the function call

 &FUNCREF(OBJECT,REGISTRY_KEY,TIMEOUT,ARG);
register_io_client(O/R,MODE,HANDLE,RFUNCREF,WFUNCREF,RWFUNCREF,ARG)
$SERVER->registerIOClient(O/R,MODE,HANDLE,RFUNCREF,WFUNCREF,RWFUNCREF,ARG)

MODE is 'r', 'w' or 'rw' depending on whether the trigger should be for input pending (read won't block), output possible (write won't block) or both. HANDLE is the fully qualified package name of the filehandle which has already been opened, on which i/o is tested. RFUNCREF, WFUNCREF and RWFUNCREF are three 'FUNCREF's (see above). If input is pending on HANDLE, this triggers the call

 &RFUNCREF(OBJECT,REGISTRY_KEY,HANDLE,ARG);

if output is possible on HANDLE, this triggers the call

 &WFUNCREF(OBJECT,REGISTRY_KEY,HANDLE,ARG);

and if both input and output won't block, then this triggers the call

 &RWFUNCREF(OBJECT,REGISTRY_KEY,HANDLE,ARG);

If MODE 'r' has been specified, then obviously only RFUNCREF can ever get called, and similarly if MODE 'w' has been specified, then only WFUNCREF can ever get called. However, if MODE 'rw' has been specified, then any of the three functions could be called depending on what becomes non-blocking first.

In all cases of MODE, all three FUNCREF's must be CODE references.

Note, unlike previous versions, now if you make multiple registrations for a specific filehandle, then client functions are still only triggered when they are guaranteed to be non-blocking. To paraphrase, if any FUNCREF is called, you are guaranteed to be able to do a sysread(), syswrite() or accept() (whichever is appropriate).

register_signal_client (O/R,SIGNAL,FUNCREF,ARG)
$SERVER->registerSignalClient(O/R,SIGNAL,FUNCREF,ARG)

SIGNAL is a valid trappable signal. The signals are obtained from the Config module. (Previous versions specified them explicitly in subroutines). The 'allSignals' method retuns the list of signals.

The client is triggered after the signal is trapped (and after the signal handler has exited). Triggering effects the function call

 &FUNCREF(OBJECT,REGISTRY_KEY,SIGNAL,NSIGS,ARG);

where

  NSIGS is the number of times the signal was
      received since this function was last called.
  and SIGNAL is the canonical name for the signal
  (which may be different from what was passed in the
  case of 'CHLD'/'CLD'. You can always use either - the
  correct signal name for the system will be used.)

Note that 'ALRM' and 'CLD' (or 'CHLD' or 'CHILD') are specially handled, and registering for these signals is of little use. For alarms, use execute_in_array_context_with_timeout(), and to find out when a child process has died, register with register_child_termination_client().

Signals which have no clients registered for them will cause the default action to occur (i.e. they will not be trapped).

Signals are not passed to the clients immediately, they are put into the queue and clients are triggered when the signal queue is checked. If you need some action to occur IMMEDIATELY on receipt of the signal, you will need to create a subclass to handle this. (This is because setting up an 'immediately signalled' type of client is fraught with difficulties, and is likely to lead to an unstable process - I tried it. And that was even without having signal handlers stacked through recursive calls to it. Mind you, it should be doable with POSIX signals, and is almost, but some bug that I haven't tracked down yet seems to propagate a die past an eval if called from within the handler, so its not yet implemented for POSIX signals in the server.)

Signal handlers are NOT installed until the server has been started (see "Starting the server").

All signal handlers are reset to default if the server loop exits (see "Questions and Answers").

See also "The SIG hash and signals".

register_child_termination_client (O/R,PID,FUNCREF,ARG)
$SERVER->registerChildTerminationClient(O/R,PID,FUNCREF,ARG)

PID is the process id of the child process. When that child dies this triggers the function call

 &FUNCREF(OBJECT,REGISTRY_KEY,DATA,ARG);

Where data is either: the process id of the terminated child; or an array reference with two items in the array - the process id and the child termination status as given by '$?' . The choice of which is returned is set by calling always_return_child_termination_status() with a boolean argument - true means return the array reference, false means return the pid only. The default is false for backward compatibility.

Note that if forking the server, you should use fork_with_child_retaining_clients() rather than just a fork().

register_event_client (O/R,EVENT,FUNCREF,ARG)
$SERVER->registerEventClient(O/R,EVENT,FUNCREF,ARG)

EVENT is any string. If any client adds the event EVENT into the server's event loop (using add_event(EVENT)) then this will trigger the call

 &FUNCREF(OBJECT,REGISTRY_KEY,EVENT,ARG);

for this client. This allows clients for user defined events

add_event (EVENT)
$SERVER->addEvent(EVENT)

Simply adds the string EVENT to the end of the event queue. Any clients waiting for this event (registered using the register_event_client() function) are triggered.

always_return_child_termination_status(BOOLEAN)
$SERVER->alwaysReturnChildTerminationStatus(BOOLEAN)

Sets whether the register_child_termination_client() call will trigger a callback with just the child's pid as the third argument (BOOLEAN true), or a reference to an array holding the pid and the termination status (BOOLEAN false). Note that this affects the call dynamically - the trigger checks as its triggering to see what type of argument it should pass.

The default is false for backward compatibility.

Client order for simultaneous events

If two events occur simultaneously, or an event occurs for which more than one client is registered, then more than one client will be triggered in the same server loop. You may want to ensure that for any pair of clients, a specific client is always called before another in this situation.

This can be achieved using the following function:

ordered_keys_ref()
$SERVER->orderedKeysRef()

This method/function returns a reference to an ARRAY type object. This object holds RegistryKey objects in whatever order you want to specify. In cases where more than one client is to be triggered within a single server loop, the order of the keys within this array determines the ordering of client activation. For example, this

 $r1 = register_...;
 $r2 = register_...;
 push(@{ordered_keys_ref()},$r2,$r1);

will ensure that in such a case, the client registered on key '$r2' will always be called before the client registered on key '$r1'.

The object returned by ordered_keys_ref() is actually an object of class EventServer::OrderedKeys, and there are several methods in this class which may make it easier for you to manipulate the array (though just treating it as an array reference is absolutely fine):

 $order = ordered_keys_ref();
 $order->push_keys(LIST_OF_KEYS);
 $order->pop_key();
 $order->shift_key();
 $order->unshift_keys(LIST_OF_KEYS);
 $order->insert_keys_before(INDEX,LIST_OF_KEYS);
 $order->delete_key_at(INDEX);

Deregistering clients

There are two methods for deregistering clients. One is to use the fact that FUNCREF calls have fatal die() errors trapped - which means that a client can die() when it is triggered, and this will cause that client to be deregistered. (Timing out will have the same effect, but is a silly way to do it since all other clients may be blocked until the timeout is finished).

NOTE that generating an 'ALRM' signal (e.g. with "kill 'ALRM,$$") will produce a die() since the alarm handler dies. This means that if you produce an ALRM signal, you are effectively timing out the client, and hence deregistering it.

The second method is to use the function/method provided:

cancel_registration (REGISTRY_KEY);
$SERVER->cancelRegistration(REGISTRY_KEY);

This deregisters the client that was registered on the key REGISTRY_KEY.

The server will deregister a client if there are any problems with it. You can find out when a client is deregistered by setting a function to be triggered when the client is deregistered using the function/method:

trigger_on_deregistering (REGISTRY_KEY,FUNCREF);
$SERVER->triggerOnDeregistering(REGISTRY_KEY,FUNCREF);

This returns true (REGISTRY_KEY) on success, false (undef) on failure. On success, the code reference FUNCREF has been added to the clients registration such that when the client is deregistered, this triggers the call:

 &FUNCREF(OBJECT,REGISTRY_KEY,method specific args,ARG);

where the 'method specific args' are determined by the type of registration used (as specified in the section "Registering clients (methods)"), and the other terms are as previously defined.

Timeouts within client code

Note alarm() should not be used (see "IMPORTANT"). Instead, a function/method has been provided which allows for nested timeouts.

execute_in_array_context_with_timeout (TIMEOUT,TRET,ERET,FUNCREF,ARGS)
$SERVER->executeInArrayContextWithTimeout(TIMEOUT,TRET,ERET,FUNCREF,ARGS)

TIMEOUT is a time (see "Times and Timing"). This sets the timeout for the call (note that times are rounded up to the next integer number of seconds);

TRET is the value/object returned as the first element of the return array if the call is timed out;

ERET is the value/object returned as the first element of the return array if the call produces a fatal error;

FUNCREF is the CODE reference which is called;

ARGS are the arguments which are passed to FUNCREF when it is called.

This method calls FUNCREF in an array context (if you want to make a call in a scalar context, wrap the function and pass the wrapped function reference, e.g.

 sub wrapper { (scalar_call(@_)) }

and FUNCREF = \&wrapper), with arguments ARGS. i.e the call is

 @ret = &FUNCREF(ARGS);

If the call is not timed out, and does not produce an error, then the array returned by the FUNCREF call (@ret) is returned. If a timeout occured, then the array (TRET) is returned, and if an error occurred during the FUNCREF call, then the array (ERET, $@) is returned.

This method allows timeouts to be nested - i.e. you can call this method within another function which is being timed out by this method.

maximum_inactive_server_time()
$SERVER->maximumInactiveServerTime()

Returns the current value that this is set to. This determines the maximum time before triggered clients are timed out. Default is 60 (seconds).

set_maximum_inactive_server_time (TIME)
$SERVER->setMaximumInactiveServerTime(TIME)

Sets this value. It should be a positive value.

Forking child processes

The call fork() works fine, but the resulting child is a copy of the server with all the clients retained. If the fork is to be followed by an exec, this is fine. But otherwise, you need to know which clients are still registered, and which ones you don't want.

Instead of worrying about this, I provide a function/method to fork the server retaining ONLY those clients you know you want. All other clients are deregistered in the child.

fork_with_child_retaining_clients (LIST_OF_REGISTRY_KEYS)
$SERVER->forkWithChildRetainingClients(LIST_OF_REGISTRY_KEYS)

This function/method works and returns as fork(): On failure, undef is returned, on success the process is forked and the child gets 0 returned while the parent gets the process id of the child returned.

In addition, only those clients with registry keys specified as arguments when this method is called, have their registration retained in the child. (Note that if you are handling signals in addition to whatever else, you may want to retain those signal handling clients in the child).

This saves you from needing to think about which clients need to be deregistered in the child - you only need to consider which ones need to be kept.

Times and Timing

Note that all times should be specified in seconds, and can be fractional (e.g. 2.35). However the fractional part may be of no use depending on where it is used.

Currently, timing-out code using execute_in_array_context_with_timeout() has values rounded up to the next highest integer , e.g. '2.35' will be used as '3', and '2' will be used as '3' (this latter use is because alarm() can be up to one second less). This is because alarm() is being used to time out code in this function, and alarm() only has a 1 second resolution.

Timing in the Interval and Timer client registration is dependent on the resolution available from a clock timer used from Perl. If the default time() is used, then fractional seconds are effectively rounded up to the next integer, since the times can only be ticked down in seconds. Resolutions will specify how many digits after the decimal point are used. The maximum resolution is one microsecond (six digits after the decimal point). Non-significant digits may be rounded up or down.

The server specifies the timing method during initialization. Currently, if syscall() and the gettimeofday() system call are available, these are used, otherwise time() is used.

However, the availability of the gettimeofday() call is established with a call to the method timeClass() in the OS specific class given by the OS name as obtained from Config, appended to 'EventServer::'.

For example, if this module is run on SunOS, Config says that the OS name ('osname' parameter) is 'sunos', in which case the call

 EventServer::sunos->timeClass()

is made. If this call produces a die(), that is trapped, and the default time class (using time()) is used. If this does not die, it is assumed to return a reference to an array, with first element being the time class to use, and the second any initialization.

For example, in the case of SunOS, this returns

 ['EventServer::Gettimeofday',116];

which specifies to use the Gettimeofday class, and initializes this class with the syscall number required to make the call to gettimeofday().

Please tell me what is best on any specific platform, I'll try to include support for it. Currently automatically supported are SunOS 4.*, IRIX 5.*, and Linux. You can add specific OS support just be adding the package and timeClass() method as shown.

Remember, you can always let it default to the plain Time class - this is usually sufficient.

The SIG hash and signals

If you assign to the %SIG hash, or install signal handlers through POSIX yourself, then you may corrupt the logic of the server. If you need to do this for anything other than a signal (e.g. __WARN__), that should be okay, otherwise you should probably create a subclass to install the handlers you want (see "Creating Subclasses").

If you want to trap a signal, do it by registering a signal client. If you want to trap a signal and need to have control during the signal handler, then subclass the EventServer class and set the handler in the subclass. And note that any handler which dies will deregister any client which sends a signal for that handler. Its usually a bad idea to do too much in a signal handler (see "Possible problems".

However, if you are definitely not going to register any clients for a particular signal, you can assign your own signal handler for that signal (though not for ALRM and CHLD).

Terminating children have their pid's removed from the process list before clients receive the 'CLD' signal. For this reason you should not wait() for terminating children. If you want to be notified of this, use the register_child_termination_client() registration method. For this reason, registering a client to receive 'CLD' signals is probably of no use.

Signals which have no clients registered for them will not be trapped.

See also "Timeouts within client code", "IMPORTANT" and the entries for methods register_signal_client() and register_child_termination_client().

Example

Note that you can execute this example with perl5 -x EventServer.pm assuming you are in the perl lib directory where you installed this module.

The example program below registers all the various types of clients.

o A timer client (expiring after 3 seconds), which is also told that it is being deregistered when it dies;

o an interval client (sending a SIGCONT every 4.3 seconds for 4 times, then deregistering) - on the fourth triggering this client calls a function to test nested timeouts. That should timeout after 3 seconds, though an interrupt could terminate it quicker;

o a signal client which also tests re-registering (triggered on receiving the first 'CONT' from the interval client, at which point it reregisters, changing the function that is called to 'cont_test2' which makes it catch the second SIGCONT from the interval client, and then deregister);

o an event client, which waits for the event 'CHECK' - that event is sent on the third triggering of the interval client. The Event client calls a nested timeout which tests the functionality of nested timeouts. That should timeout after 3 seconds, though an interrupt could terminate it quicker;

o an i/o client, which waits for some input on STDIN (requires a <RETURN> to be triggered) and then deregisters;

o a child termination client (the process forks right at the beginning, and the child sleeps for 10 seconds then terminates);

o and finally another signal client which will take two SIGINT's (usually generated by typing cntrl-C) then deregisters, which means that the next SIGINT will cause the default signal action to occur (program termination).

Note that the server will terminate when all clients are deregistered so if you want to see everything you need to run this at least twice - once you can terminate by giving three cntrl-C's BEFORE all the other clients have deregistered (you can keep the io client registered by not typing <RETURN>), and the second time you can let the program terminate by letting all the clients deregister (two cntrl-C's and a <RETURN> get rid of the SIGINT client and the io client - all other clients get deregistered within the first 20 seconds).

#!perl5

 BEGIN {print "Initializing, process id is $$\n";}
 use EventServer;
 
 # Timer test client (after 3 seconds)
 $r = register_timed_client([],3,sub {print STDERR "Timed test\n"})
        || die "Timed test not registered";
 
 # Deregistering Trigger test
 trigger_on_deregistering($r,
   sub {print STDERR "Deregistering Trigger test\n"}) ||
         die "Deregistering Trigger test not registered";
 
 # Interval test client (every 4.3 seconds, 4 times)
 register_interval_client([],4.3,\&interval_test)
        || die "Interval test not registered";
 
 sub interval_test {
     $C++;print STDERR "Interval test $C\n";
     kill 'CONT',$$;
     if ($C == 3) {
         add_event('CHECK');
     } elsif ($C > 3) {
         $t=time;
         execute_in_array_context_with_timeout(2.5,0,0,\&t4_test);
         print STDERR 'Nested timeout returned after ',time-$t," secs\n";
         die;
     }
 }
 
 sub t3_test {
     execute_in_array_context_with_timeout(2.5,0,0,
                                           sub {select(undef,undef,undef,9)});
 }
 
 sub t4_test {
     execute_in_array_context_with_timeout(6.5,0,0,
                                           sub {select(undef,undef,undef,9)});
 }
 
 sub t1_test {
     print STDERR "Event client test\n";
     $t=time;
     execute_in_array_context_with_timeout(6.5,0,0,\&t3_test);
     print STDERR 'Nested timeout returned after ',time-$t," secs\n";
     die;
 }
 
 register_event_client([],'CHECK',\&t1_test) ||
    die "Event test not registered";
 
 # Signal test client (once after first Interval test)
 $r = register_signal_client([],'CONT',\&cont_test)
        || die "Signal test not registered";
 
 # Reregistration test client (once after second Interval test)
 sub cont_test {
   print STDERR "Signal test\n";
   register_signal_client($r,'CONT',\&cont_test2)
 }
 sub cont_test2 {print STDERR "Reregistering test\n";die}
 
 # IO test client (once after user types <RETURN>)
 register_io_client([],'r',STDIN,\&io,\&io,\&io) || 
        die "STDIN test not registered";
 sub io {$l=<STDIN>;print STDERR "IO test: $l";die}
 
 # Child Termination test client (after 10 seconds)
 defined($pid = fork) || die "Couldn't fork";
 if($pid==0){
   #Keep the child around for 10 seconds
   $SIG{'INT'} = 'IGNORE';sleep(10);warn "Child Died\n";exit(23);
 }
 print STDERR "Start child process pid = $pid\n";
 always_return_child_termination_status(1);
 register_child_termination_client([],$pid,
   sub {print STDERR "Child (pid=$_[2]->[0]) terminated with status ",$_[2]->[1]>>8,"\n"}) ||
        die "Not registered";
 
 # Signal test client (catches 2 ^C, then uses default SIGINT)
 register_signal_client([],'INT',
   sub {$A++;print STDERR "INT caught $A\n";$A>1 && die})
        || die "Signal test not registered";
 
 print "Starting server now\n";
 start_server();
 

__END__

Creating Subclasses

The EventServer server is designed with subclassing in mind. There is only so much generality that can be catered for in any class, and specific applications will do much better by subclassing and specializing.

In making a subclass of the server, the following points are of note:

1. The server class is specified in the variable

 $EventServer::SERVER_CLASS.

To allow your subclass to handle ALL methods (including signal handling, initialization and exporting of functions) you need to specify this variable before require'ing the EventServer. This is best done as

 package MyServer;
 BEGIN {$EventServer::SERVER_CLASS ||= MyServer;}
 @ISA = qw(EventServer);
 require EventServer;

Note that the @ISA call _MUST_ be before the 'require' since the require contains initialization calls that need to do method lookups on $EventServer::SERVER_CLASS.

Making the assignment conditional on the variable being false allows your class to be subclassed as well.

2. The initialization is a method called init(). Specifying the SERVER_CLASS variable above will ensure that the init method is called in the subclass rather than the EventServer class.

Initialization occurs when EventServer is require'd.

3. The initialization sets several system constants:

 EINTR EBADF EINVAL EFAULT WNOHANG

and will produce a fatal error if they cannot be set.

These are set when the method _setConstantsAndTimeClass() is called from init(), which in turn calls _setConstants(). The constants are set using the methods _setEINTR(), _setEBADF(), _setEINVAL(), _setEFAULT(), and _setWNOHANG().

So, for example, to specify the values for SunOS 4, you could declare the following method in a subclass:

 sub _setConstants {
    my($self) = @_;
    $self->_setEINTR(0x4);
    $self->_setEBADF(0x9);
    $self->_setEINVAL(0x16);
    $self->_setEFAULT(0xe);
    $self->_setWNOHANG(0x1);
 }

4. The initialization sets and initializes the variable time class to use. It does this by finding the OS name from Config ($Config{'osname'}) and making the call:

 EventServer::<osname>->timeClass()

where <osname> is the OS name as found from CONFIG. If this call does not die() (any call to die() is trapped), then it is assumed to return an array reference to an array consisting of the time class to use as the first element, and values to initialize the time class for subsequent elements.

Typically, this would be 'EventServer::Gettimeofday' as the first element, and the syscall number for the gettimeofday call as the second element (e.g. SYS_gettimeofday from syscall.h on many systems). However, you could explicitly specify the default 'EventServer::Time' using this method, or a completely different class.

If you roll your own time class, it must have the following methods implemented appropriately:

 initialize(?)          # Whatever
 now()                  # Return an object representing the time now
 newFromSeconds(SECONDS)# Return an object representing SECONDS
 copy()                 # Return new object representing the time in 'self'
 newFromDiff(OTHER)     # Return an object representing the time difference 
                        # between 'self' and OTHER
 original()             # Return the time in its original format
 isPositive             # Is the time positive? Return boolean
 smallerTime(OTHER)     # Return object with smaller time, 'self' or OTHER
 time()                 # Return the time as a number (a float if needed)
 wholeSecondsRoundedDown()# Return time as an integer, ignoring fractions

The method timeClass() gives the class being used to handle times. Available are EventServer::Time using the time() function in Perl (resolution 1 second) and EventServer::Gettimeofday which uses the gettimeofday() C system call using syscall.

5. The init() sets the list of signals that can be registered for. The list is obtained from the Config module, minus the untrappable KILL and STOP signals.

6. The setSignalHandlers() method

The setSignalHandlers() method creates the signal handlers if necessary, and installs those that are to be permanently installed. All signals have a signal handler assigned.

Unlike previous versions, in order to elminate possible reentrancy bugs, the signal handlers do not execute in subclasses. They are functions in their own namespace which do the absolute minimum possible (mostly just incrementing a variable).

To reimplement a signal handler, you need to respecify the signalHandlerFor() method. This method takes as argument the signal name, and returns the name of the handler. The handlers should increment the global $EventServer::Signal::<SIGNAME>, e.g. the 'TERM' signal handler should increment the global $EventServer::Signal::TERM. (This is all they do by default).

The ALRM handler is implemented slightly differently, and should not be reimplemented unless you know what you're doing.

Handlers are normally only installed when a client registers for that signal. However, ALRM and CHLD are permanently registered. You can specify which handlers are permanently registered by reimplementing the isSpecialSignalHandler() method. This returns true for those signals which should have permanently installed handlers. But note that if you reimplement this, you should include ALRM and CHLD (or CLD) among the set of signals which return true.

Note that any handler which is set to die on receipt of a signal will deregister any client which sends a that signal.

7. The server can be started using

 start_server();

or

 EventServer->startServer();

or

 MyServer->startServer();

since startServer() actually starts the server using the class specified in $EventServer::SERVER_CLASS

Example subclasses

The SunOS example is not necessary, and is just here for illustrative purposes (though can be used).

 ############################################################
 # Subclass for SunOS4. Speeds up initialization and
 # ensures the use of gettimeofday(2) system call.
 # Also SunOS doesn't need to have handlers reinstalled
 # when they are called.
 # NOTE that you can use EventServer
 # on SunOS or any other OS without this subclass.
 # 
 package EventServer_SunOS4;
 
 BEGIN {
   if (`/bin/uname -sr` =~ /^SunOS\s+4/i) {
      $EventServer::SERVER_CLASS ||= 
        EventServer_SunOS4;
   } else {
      warn "Warning: system is not SunOS4 - using plain EventServer class\n";
   }
 }
 
 @ISA = qw(EventServer);
 require EventServer;
 
 sub _setConstantsAndTimeClass {
     my($self) = @_;
     $self->_setConstants();
     $self->_setTimeClass(EventServer::Gettimeofday,116);
 }

 sub _setConstants {
     my($self) = @_;
     $self->_setEINTR(0x4);
     $self->_setEBADF(0x9);
     $self->_setEINVAL(0x16);
     $self->_setEFAULT(0xe);
     $self->_setWNOHANG(0x1);
 }
 
 # No need to reset signal handlers within signal handlers for SunOS
 # Though this is redundant, since POSIX handlers will be used anyway.
 sub signalHandlerForSpecialSignal {
     my($self,$signal) = @_;
     $signal =~ tr/A-Z/a-z/;
     'EventServer::Signal::posix_' . $signal;
 }
 sub defaultSignalHandlerFor {
     my($self,$signal) = @_;
 
     my $handler = $self->_handlerPrefix() . $signal;
     unless ( defined(&{$handler}) ) {
         eval sprintf('sub %s {$%s++;die "\n"} $%s=0;',
                     $handler,$handler,$handler);
     }
     $handler;
 }
 
 1;
 __END__

Possible problems

Posting from Todd Hoff

 >From: tmh@ictv.com (Todd Hoff)
 Newsgroups: comp.lang.perl
 Subject: Re: Perl 5: alarm BSD vs. SysV
 Date: 3 Apr 1995 10:38:35 -0700
 Organization: ICTV, Inc.
 Lines: 24
 Message-ID: <3lpbqr$gbm@anxious.ictv.com>
 
 In article <3lomapINN334@calvin.lif.icnet.uk>,
 >Have you guys tried re-setting the signal handler within the
 >handler. Some systems reset the signal handler to default
 >after it is called.
 >
 >sig handler {
 >   $SIG{'ALRM'} = 'handler';
 >   ...
 >}
 
 Each UNIX vendor has chosen which version of the "old" signal semantics
 to emulate, thus signal work is not very portable and bug prone.
 Setting the handler in the handler breaks miserably because an interrupt
 can occur before the handler is set. What sucks is that you are
 unlikley to see problems unless you have a loaded machine or
 high interrupt rate, both of which i usually have :-(
 
 The only solution is for perl to use POSIX signals which are safe 
 (but harder to understand). As an aside do not do anything in a signal 
 handler but set a flag which tells you if you should call a handler
 in the main line logic. Reentrancy bugs are intermitent and nasty.
 -- 
 Todd Hoff     | My words are my own.
 tmh@ictv.com  | And i have all this extra white space...

In addition, perl has the problem that signals can interrupt a malloc - and this seems prone to causing a SIGSEGV.

The problems are decreased in this server because most of the time it will probably be in the select call, in which case signals are likely to hit it mostly during a select call, not a malloc. But you should be prepared for your server to die, and have some automated procedure to restart it - like a cron job. This is a general problem of signals and perl (and C), not a specific problem of the server.

If you want the general problem illustrated in a simple way, the following is nice and clear, and will give a core dump after a few seconds:

 @a = qw(1, 2, 3, 4);
 $sig_happened = 0;
 
 $SIG{'ALRM'} = 'sig_handler';
 alarm(1);
 
 while (1)
 {
     foreach $z (@a)
     {
        reset_handler() if ($sig_happened);
     }
 }
 
 sub reset_handler
 {
     print "Reset the handler\n";
     $sig_happened = 0;
     $SIG{'ALRM'} = 'sig_handler';
     alarm(1);
 }
 
 sub sig_handler
 {
     $sig_happened = 1;
 }
__END__

Questions and Answers

Q1. How do I exit the start_server loop.

A1. When there are no more clients registered with the server, the method noClients() is called. If this method returns a false value then the start_server loop terminates. If this returns a true value, then the loop continues.

The default action is for the server to print the message

 Error: No clients are registered with the server ...

to STDERR and then exit.

To change the default behaviour, create a subclass which redefines noClients, and use that subclass. For example

 package MyServer;
 BEGIN {$EventServer::SERVER_CLASS ||= MyServer;}
 @ISA = qw(EventServer);
 require EventServer;
 sub noClients {0} # Just terminate the loop if no clients left.

Note that you don't need this to go into a separate module - it can be in your main program as an initialization if this is all you need, e.g.

 $EventServer::SERVER_CLASS ||= MyServer;
 @MyServer::ISA = qw(EventServer);
 require EventServer;
 sub MyServer::noClients {0}

AUTHOR

This software was developed by Jack Shirazi in the Biomedical Informatics Unit at the Imperial Cancer Research Fund, and was partly funded by the European Union Computer Executive Committee under EP6708 `APPLAUSE: Application and Assessment of Parallel Programming Using Logic'.

COPYRIGHT

Copyright 1995 Imperial Cancer Research Fund, UK. All rights reserved.

This software is distributed under the same terms as Perl.

This program is free software; you can redistribute it and/or modify it under the terms of either:

a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or

b) the "Artistic License" which comes with Perl.

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. See either the GNU General Public License or the Artistic License for more details.

MODIFICATION HISTORY

Future Versions (to do)

Use setitimer if available. Make into SelfLoader class(es) when SelfLoader comes on stream? Allow clients option of executing in own process (psuedo-threaded). Global store across processes. Mobile server. Shadow server. Automatic termination on dregistration of specific clients.

Version 2.3, 5th February 1996

Added option to return child termination status. Fixed missing assignment of arguments in EventServer::_resetVectors which prevented closed handles from being deregistered. Made into EventServer package only, with Server::Server::EventDriven subclass for backward compatibility. Fixed docs. Fixed posix_alarm and resetting_alarm to posix_alrm and resetting_alrm. Altered registering semantics in-line with docs (was not returning existing registry key for identical registration).

Version 2.2, 8th August 1995

Bug fix - isReady had wrong number of args.

Version 2.1, 10th July 1995

Altered signal installers to make SEGV, ILL, PIPE and BUS special handlers which are permanently installed. Added 'Free software' copyright.

Version 2.0, 14th June 1995

Added Andrew Wilcox patch to fix recursive includes in _tryH. Invalid arguments to register methods now produce a croak. Added Linux subclass (courtesy of Andrew Wilcox). Added Exportable function wrappers to methods. Enabled server loop to terminate. Podified, cleaned and added to documentation. Removed any redundant %SIG 'tie' code. Added dummy buffer for gettimeofday syscall to workaround perl 5.000 (and 5.001?) bug - now works with any perl5 version.

Changed constants retrieval to not try so hard - now just looks at POSIX, Errno and Wait (in perl4 & 5 versions) and uses those - also uses classes to get gettimeofday syscall value. Now uses signals listed in Config. Now asks the os specific class (obtained from Config) for time class and any time class initialization.

Rewrote sig handlers to mostly do nothing except set a global. Rewrote and modularized server loop so that it is easier to alter the behaviour in a subclass. Wrapper objects now trigger on same 'trigger' method. Loop goes through one iteration, then triggers all clients in a user defined order (or random order for any not in the user defined order). IO clients are guaranteed to be triggered only if ready - even if multiple clients are registered on the same handle.

Added support for POSIX signals - uses them if available. Fixed leaked alarm time logic. Added nested alarm time tests to example. Changed all classes to be nested under EventServer. Changed registry keys to be RegistryKey objects. Added client defined events. Altered documentation. Put server loop in eval loop. Added signal unblocking to handle IRIX bug.

Version 1.2, 10th April 1995

Altered various internal methods (mainly associated with init) to allow subclassing to be more straightforward. Provided example subclasses for SunOS4 and IRIX5. Altered signal handlers to reset signal handler after being called to provide support for systems which need it. Removed tie on %SIG due to flakiness (%SIG no longer read-only). Moved the methods required by tie into a separate package space. Altered _tryH and _tryCompiling. Fixed bug in executeInArrayContextWithTimeout (wasn't handling recursive timeouts !). Made 'use strict' and -w clean (though the filehandles need 'no strict' in 3 places). Documentation altered.

Version 1.1, 5th April 1995

EventServer::Time::copy & EventServer::Time::newFromDiff bugfixed, EventServer::_noClients error message changed, added triggerOnDeregistering method and support methods and altered documentation appropriately.

Version 1.0, 10th March 1995

Base version.