Daemons are programs that are always running. At startup they might need to set their UID and GID and detach from the console to run in the background. Daemons are often multi-process and require process management and babysitting. While it is expected that daemon will receive requests via a socket or other mechanism, PoCo::Daemon will not deal with this. It expects to be told of certain things later on. Much of this is probably a dup of stuff in Net::Daemon, POE::Component::Server::PreforkTCP and others. I am not in fact going to rewrite it but rather pull the code I've already written out of IKC and modularize it. There are roughly 3 types of process management: 1- Single process All sessions live in one process. This process does pretty much everything, maybe with a RunQueue. 2- Post-forking A new process is forked off every time a request comes in. 3- Pre-forking A pool of processes is maintained. These sub-processes handle a number of requests, then exit. Single Process ============== Your sessions have to be fully cooperative. If an event blocks, it prevents all other events from running. You will probably only use UID, GID and detach functions. Post-forking ============ Pre-forking =========== A pool of processes is created at startup. Scoreboard ========== Concept borrowed from Apache : an array of SYSV shared memory is used to keep track of what each child process is doing. This, along with SIGCHLD, is how a sub-process will tell the parent to fork a new one. Each slot in the scoreboard represents a child-process (not it's PID, however). The states are : FORK set by parent, to indicate it's about to fork this one. This is the only time parent will write to scoreboard. fork set by child after fork 0,1,2,3,4 waiting for request # req processing request long process will take a long time. Don't babysit me exit exiting . done, exited Scoreboard is checked very X milliseconds to see if more spare children are need (pre-forking). BABYSITTING =========== Every so often, PoCo::Daemon will make sure that children are behaving. It does this with Proc::ProcessTable. Will check for zombie children, if a request has rogue (ie, taking longer then max_time). Rogue processes are sent a SIGINT. If that doesn't work, SIGTERM and then SIGKILL are tried. If it still doesn't work, we give up and ignore it. API === Startup ------- PoCo::Daemon->spawn( GID=> # change to these IDs UID=> alias=> # alias of daemon processes. defaults 'daemon' registry_key=> # key used in the registry for multicast # events. Defaults to alias logfile=> # filename or wheel that STDERR and STDOUT are set to detach=> # boolean deciding if we do fork/fork/setgid # process management max_children=> # absolute maximum number of processes start_children=> # this triggers pre-forking min_spare=> # this amount # spare processes max_spare=> # don't have more then this # spare requests=> # number of requests a sub-process should # handle before exiting # babysitting babysit=> # delay between babysitting runs, activates # babysitting max_time=> # max time a request can take before we get # worried. defaults to 10 minutes ); Status maintenance ------------------ Daemon->status($status[, $param]); $kernel->post(daemon=>'status', $status[, $param]); $status is one of : 'wait' waiting for a new process Updates scoreboard {pre,post}-fork, exit if # requests are completed 'done' synonym for 'wait', makes more sense when post-forking 'req' a new request has arrived Updates scoreboard post-fork, forks a new process, after the fork, a daemon_child event is sent w/ $param. Users must wait for that event before processing. 'long' request will take a long time. Don't wait up! Updates scoreboard $param is sent with the 'daemon_child' event as-is. It is expected that you will post to daemon/status from your POE::Wheel::SocketFactory('SuccessEvent') or some such. In the case of a post-forked server, you must wait for the daemon_child event to process the request. Status maintenance is one place where maybe a call() would be appropriate. Introspection ------------- Would it be useful to have functions to modify/query config options at run time? Killing floor ------------- Daemon->forked($pid); $kernel->post(daemon=>'forked', $pid); Normaly, if poco::daemon receives a SIGCHLD for a PID it doesn't know about it will complain (but not die). This tells it to ignore SIGCHLD for that pid. This is just me being anal. Bed time -------- Daemon->shutdown(); $kernel->post(daemon=>'shutdown'); Request this session disappear so that the kernel can exit. The scoreboard is updated to 'exit'. The scoreboard is set to '.' in an END{} block. This then multicasts 'deamon_shutdown' to everyone. It is IMPORTANT to call this as soon as you meet an error. For instance, you or socketfactory's ErrorEvent. Otherwise, it will blithely continue forking off child processes. EVENTS ====== PoCo::Daemon will multicast these events using $kernel->signal() when certain things happen. 'daemon_start' Daemon has been set-up successfully. Initial forking will start soon/has started. 'daemon_parent' Tells sessions that they are in the parent process. Initial forking has completed. 'daemon_child'[, $param] Tells sessions that they are in a child process. For post-fork servers, it has one parameter : whatever was sent to Daemon->status with if $status = 'req' 'daemon_shutdown' Tells sessions that it's time to hand in the towel. In child, this means either max requests handled (pre-forking) or that the the request is done (post-forking). In parent, this means Daemon->shutdown was called. EXAMPLES ======== Create a pre-forking server that stays in the fore-ground so that it can be run from DJB's daemontools. Each child will only process 5 requests before exiting. use PoCo::Daemon; PoCo::Daemon->spawn( logfile=>'/var/log/honkhonk', max_children=>100, start_children=>5, min_spare=>2, max_spare=>10, requests=>5, ); POE::Session->create( inline_states=>{ _start=>sub { # ... create a SocketFactory wheel, but don't allow it to # select $heap->{wheel}=POE::Wheel::SocketFactory->new( BindPort=>1234, BindAddress=>'127.0.0.1', SuccessEvent=>'accept', ); $heap->{wheel}->pause_accept(); $kernel->sig('daemon_child'=>'started'); $kernel->sig('daemon_shutdown'=>'game_over'); }, started=> sub { # called to tell us we are the child. This # turn the SocketFactory on and process requests $heap->{wheel}->resume_accept(); $kernel->post(daemon=>'status', 'wait'); # .... } # socketfactory got a connection handle it here accept=>sub { # honking and more honking $kernel->post(daemon=>'status', 'req'); # socketfactory should be turned off } # request is finished processing done => sub { # cleanup $kernel->post(daemon=>'status', 'wait'); } # if we've done enough requests, time to exit game_over => sub { # do some clean up # kill socketfactory # remove aliases, etc } }); ---------------------------------------------------------------- Post-forking example use PoCo::Daemon qw(Daemon); PoCo::Daemon->spawn( logfile=>'/var/log/honkhonk', max_children=>100, ); POE::Session->create( inline_states=>{ _start=>sub { # ... create a SocketFactory wheel $heap->{wheel}=POE::Wheel::SocketFactory->new( BindPort=>1234, BindAddress=>'127.0.0.1', SuccessEvent=>'accept', ErrorEvent=>'error', ); $kernel->sig('daemon_child'=>'request'); }, # socketfactory got a connection handle it here accept=>sub { # honking and more honking # $rid is a request ID, a key into the $heap Daemon->status('req', $rid); delete $heap->{wheel}; } # we are now the child process request=>sub { my $rid=$_[ARG0]; # passed to us from accept above # we block for a while, being to lazy to make an un-blocking version Daemon->status('long'); # ..... work like mad Daemon->status('done'); } error=>sub { my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0, ARG1, ARG2]; if($errnum==EADDRINUSE) { # EADDRINUSE Daemon->shutdown(); # THIS IS IMPORTANT } }, });