in reply to Re: Problems with forking
in thread Problems with forking
Didn't know that one. Thanks.
Personally, i use the ActiveState development kit. Especially on windows, it takes out a lot of hassle when writing services and tray applets.
For my Maplat project, i wrote my own service start/stop/watchdog thingy. This is still only in my DarkPAN repository.
...what the hell. i'm just gonna release it right here (i just copy it from my repo, you may/will have to tweak it to your needs). One caution, though: You need a running memcached as well as the Maplat Framework installed.
Here's the main program:
#!/usr/bin/perl -w # MAPLAT (C) 2008-2009 Rene Schickbauer # Developed under Artistic license # for Magna Powertrain Ilz use 5.010; use strict; use warnings; BEGIN { unshift @INC, "/home/cavac/src/maplat_prodit/server"; unshift @INC, "/home/cavac/src/maplat_prodit/lib"; unshift @INC, "/home/cavac/src/maplat_logging/lib"; unshift @INC, "/home/cavac/src/maplat_framework/lib"; } use MaplatSVCLinux; use XML::Simple; use Maplat::Helpers::Logo; our $APPNAME = "Maplat SVC"; our $VERSION = 0.995; MaplatLogo($APPNAME, $VERSION); our $isCompiled = 0; if(defined($PerlApp::VERSION)) { $isCompiled = 1; } our $cycleStartTime = 0; # ------------------------------------------ # MAPLAT - Service/Daemon for Linux # ------------------------------------------ my $action = shift @ARGV; if(($action ne "start" && $action ne "stop" && $action ne "reset")) { print "ARGV: " . $#ARGV . "\n"; print("Usage:\n\tmaplat_svc_linux.pl [start|stop|reset] config.xml +\n"); exit(1); } my $configfile = shift @ARGV; print "Loading config file $configfile\n"; my $config = XMLin($configfile, ForceArray => ['module', 'run'],); if(defined($config->{basedir})) { print "Changing dir to " . $config->{basedir} . "\n"; chdir $config->{basedir}; } my @modlist = @{$config->{module}}; my $svcserver = new MaplatSVCLinux(0, $config->{basedir}, $config->{memhserver}, $config->{memhnamespace}, $APPNAME, $VERSION, $isCompiled, ); if($action eq "stop") { my $status = $svcserver->getServerStatus(); if($status eq "stopping") { print "STOP already requested\n"; exit(0); } elsif($status eq "stopped") { print "Service not running\n"; exit(0); } $svcserver->requestStop(); print "STOP requested\n"; while(1) { $status = $svcserver->getServerStatus(); last if($status eq "stopped"); print "Waiting for service to stop...\n"; sleep(1); } print "Service shut down.\n"; exit(0); } if($action eq "reset") { print "'Manually' resetting service status fields...\n"; #$svcserver->requestStop(); $svcserver->setServerStatus("stopped"); sleep(5); my $status = $svcserver->getServerStatus(); if($status eq "stopped") { print "Reset seems to have worked.\n"; } else { print "Failed: Got back status '$status'\n"; } exit(0); } # Configure run-once scripts $svcserver->setServerStatus("starting"); if(defined($config->{startup}->{run})) { foreach my $script (@{$config->{startup}->{run}}) { $svcserver->configure_startup($script); } } if(defined($config->{shutdown}->{run})) { foreach my $script (@{$config->{shutdown}->{run}}) { $svcserver->configure_shutdown($script); } } foreach my $module (@modlist) { $svcserver->configure_module($module); } $svcserver->endconfig(); $svcserver->setServerStatus("running"); my $loopcount = 0; while (!$svcserver->shouldStop()) { my $workCount = $svcserver->work(); sleep(1); # Just in case - set our status to running as long as the main loo +p # runs. This helps prevent some troubles $svcserver->setServerStatus("running"); $loopcount++; if($loopcount == 100) { print "...ping...\n"; $loopcount = 0; } } $svcserver->setServerStatus("stopping"); $svcserver->shutdown; $svcserver->setServerStatus("stopped");
And the package that does the actual work:
package MaplatSVCLinux; use strict; use warnings; use Maplat::Helpers::Cache::Memcached; use Maplat::Helpers::BuildNum; use Unix::PID; sub new { my ($class, $isService, $basePath, $memhserver, $memhnamespace, $APPNAME, $VERSION, $isCompiled) = @_; my $self = bless {}, $class; $self->{isService} = $isService; $basePath =~ s/\//\\/g; # Convert to Win32 Path $self->{basePath} = $basePath; if($memhserver ne "none") { my $memd; my $memd_loaded = 0; # Decide which Memcached module we want to use # First, we try the festest one, then the standard # one and if everything fails we use our own if(eval('require Cache::Memcached::Fast')) { print " Cache::Memcached::Fast available.\n"; $memd = new Cache::Memcached::Fast { servers => [ $memhserver ], namespace => $memhnamespace . "::", connect_timeout => 0, }; $memd_loaded = 1; } elsif(eval('require Cache::Memcached')) { print " No Cache::Memcached::Fast ... falling back to C +ache::Memcached\n"; $memd = new Cache::Memcached { servers => [ $memhserver ], namespace => $memhnamespace . "::", connect_timeout => 0, }; $memd_loaded = 1; } else { print " No Cache::Memcached* available ... will try to +use MaplatHelpers::Cache::Memcached\n"; } # Check if the selected Memcached lib is working correctly my $key = "test_" . int(rand(10000)) . "_" . int(rand(10000)); my $val = "test_" . int(rand(10000)) . "_" . int(rand(10000)); my $newval; if($memd_loaded) { $memd->set($key, $val); $newval = $memd->get($key); } if(!defined($newval) || $newval ne $val) { if($memd_loaded) { print " Selected Memcached client lib is broken - f +alling back to MaplatHelpers::Cache::Memcached\n"; } $memd = new MaplatHelpers::Cache::Memcached { servers => [ $memhserver ], namespace => $memhnamespace . "::", connect_timeout => 0, }; $memd->set($key, $val); $newval = $memd->get($key); if(!defined($newval) || $newval ne $val) { die("Maplat Memcached client lib is also broken - givi +ng up!"); } else { $memd->delete($key); } } else { $memd->delete($key); } print " Selected Memcached library seems to be working. Goo +d!\n"; $self->{memd} = $memd; $self->memhset("VERSION::" . $APPNAME, $VERSION); $self->memhset("BUILD::" . $APPNAME, readBuildNum(undef, $isCo +mpiled)); $self->memhdelete("StopSVC"); $self->{is_configured} = 0; } return $self; } sub requestStop { my ($self) = @_; my $tmp = 1; $self->memhset("StopSVC", $tmp); } sub shouldStop { my ($self) = @_; my $stop = $self->memhget("StopSVC"); if(!defined($stop) || $stop != 1){ return 0; } else { return 1; } } sub setServerStatus { my ($self, $status) = @_; $self->memhset("SVCRunningStatus", $status); } sub getServerStatus { my ($self) = @_; my $status = $self->memhget("SVCRunningStatus"); if(!defined($status)){ return "stopped"; } else { return $status; } } sub startconfig { my ($self) = @_; $self->{apps} = (); $self->{startup_scripts} = (); $self->{shutdown_scripts} = (); } sub configure_module { my ($self, $module) = @_; print "Configuring module " . $module->{description} . "...\n"; $module->{handle} = undef; my $fullapp = $module->{app}; $module->{app} = $fullapp; my $fullconf = $module->{conf}; $module->{conf} = $fullconf; push @{$self->{apps}}, $module; } sub configure_startup { my ($self, $command) = @_; $command =~ s/\//\\/g; push @{$self->{startup_scripts}}, $command; } sub configure_shutdown { my ($self, $command) = @_; $command =~ s/\//\\/g; push @{$self->{shutdown_scripts}}, $command; } sub endconfig { my ($self) = @_; # "Don't fear the Reaper" $SIG{CHLD} = 'IGNORE'; foreach my $script (@{$self->{startup_scripts}}) { $self->run_script($script); } print "Startup scripts complete\n"; foreach my $app (@{$self->{apps}}) { $self->start_app($app); } print "Initial apps startup complete\n"; $self->{shutdown_complete} = 0; $self->{is_configured} = 1; } sub work { my ($self) = @_; my $workCount = 0; foreach my $app (@{$self->{apps}}) { if(!$self->check_app($app)) { print "*** App " . $app->{description} . " FAILED! ***\n" } $workCount++; } return $workCount; } sub shutdown { my ($self) = @_; if($self->{is_configured} == 1) { print "Shutdown started.\n"; foreach my $app (@{$self->{apps}}) { $self->stop_app($app); } print "Apps shut down.\n"; foreach my $script (@{$self->{shutdown_scripts}}) { $self->run_script($script); } print "Shutdown scripts complete\n"; } $self->{shutdown_complete} = 1; return; } sub DESTROY { my ($self) = @_; if(!$self->{shutdown_complete}) { $self->shutdown(); } } sub check_app { my ($self, $app) = @_; if(!defined($app->{handle})) { return $self->start_app($app); } my $checker = Unix::PID->new(); # First, check if the process exited if(!$checker->is_pid_running($app->{handle})) { # Process exited, so, restart print "Process exit detected: " . $app->{description} . "!n"; return $self->start_app($app); } if(!defined($app->{lifetick}) || $app->{lifetick} == 0) { return 1; } else { # Process itself is still running, so check its lifetick # to see if it hangs my $pid = $app->{handle}; my $apptick = $self->memhget("LIFETICK::" . $pid); if(defined(!$apptick)) { #print "Apptick not set for " . $app->{description} . "!\n +"; return 1; } elsif($apptick == 0) { # Client requested a temporary suspension of lifetick hand +ling return 1; } my $tickage = time - $apptick; if($tickage > $app->{lifetick}) { # Stale lifetick print "Stale Lifetick detected: " . $app->{description} . +"!\n"; $self->stop_app($app); return $self->start_app($app); } else { return 1; } } } sub start_app { my ($self, $app) = @_; my $pid = fork(); if($pid) { #parent print "Forked " . $app->{app} . " has PID $pid\n"; $app->{handle} = $pid; my $stime = time; $self->memhset("LIFETICK::" . $pid, $stime); } else { # Child exec($app->{app} . " " . $app->{conf}) or die("Can't exec"); print "Child done\n"; exit(0); } } sub stop_app { my ($self, $app) = @_; if(defined($app->{handle}) && $app->{handle}) { my $pid = $app->{handle}; print "Killing app " . $app->{description} . " with PID $pid.. +.\n"; kill 15, $pid; # SIGTERM sleep(2); kill 9, $pid; #SIGKILL $app->{handle} = undef; print "...killed.\n"; $self->memhdelete("LIFETICK::" . $pid); } else { print "App " . $app->{description} . " already killed\n"; } } sub run_script { my ($self, $command) = @_; print "Running command '$command':\n"; my @lines = `$command`; foreach my $line (@lines) { chomp $line; print ":: $line\n"; } return 1; } sub memhget { my ($self, $key) = @_; return if(!defined($self->{memd})); $key = $self->memhsanitize_key($key); return $self->{memd}->get($key); } sub memhset { my ($self, $key, $data) = @_; return if(!defined($self->{memd})); $key = $self->memhsanitize_key($key); return $self->{memd}->set($key, $data); } sub memhdelete { my ($self, $key) = @_; return if(!defined($self->{memd})); $key = $self->memhsanitize_key($key); return $self->{memd}->delete($key); } sub memhsanitize_key { my ($self, $key) = @_; # Certain chars are not allowed in keys for whatever reason. # This *should* be handled by the Cache::Memcached module, but isn +'t # We handle this by substituting them with a tripple underline $key =~ s/\ /___/go; return $key; } 1;
All configuration is done in a simple XML file (which is why i use XML::Simple... harhar):
<maplatsvc> <basedir>/home/cavac/src/maplat_prodit/server</basedir> <memhserver>127.0.0.1:11211</memhserver> <memhnamespace>RBSMem</memhnamespace> <!-- <startup> <run>start.sh</run> </startup> <shutdown> <run>shut.sh</run> <run>shut.sh</run> </shutdown> --> <module> <description>WebGui</description> <app>perl webgui_cmd.pl</app> <conf>configs/rbswebgui.xml</conf> <lifetick>0</lifetick> </module> <module> <description>WebGuiSSL</description> <app>perl webgui_cmd.pl</app> <conf>configs/rbswebgui_ssl.xml</conf> <lifetick>0</lifetick> </module> <module> <description>RBS Worker</description> <app>perl worker_cmd.pl</app> <conf>configs/rbsworker.xml</conf> <lifetick>120</lifetick> </module> <module> <description>Uncritical Worker</description> <app>perl worker_cmd.pl</app> <conf>configs/uncriticalworker.xml</conf> <lifetick>600</lifetick> </module> <module> <description>TimeServer</description> <app>perl timeserver_cmd.pl</app> <conf></conf> <lifetick>60</lifetick> </module> </maplatsvc>
The svc program does not detach itself from the command line, here's a sample script to start all services. Most of it's work is to set the correct path for my perl interpreter.
#!/bin/bash # ActiveState Perl export PATH=/home/cavac/bin/ActivePerl-5.12/site/bin:/home/cavac/bin/A +ctivePerl-5.12/bin:$PATH export MANPATH=/home/cavac/bin/ActivePerl-5.12/site/man:/home/cavac/bi +n/ActivePerl-5.12/man:$MANPATH # ActiveState PDK export PATH=/home/cavac/bin/PDK/bin:$PATH export MANPATH=/home/cavac/bin/PDK/share/man:$MANPATH # ActiveState Komodo export PATH=/home/cavac/bin/Komodo-5/bin:$PATH cd /home/cavac/src/maplat_prodit/server perl maplat_svc_linux.pl start configs/rbssvc.xml &>/dev/null & echo MAPLAT Background startup initiated...
When you don't want to background (like in this example, when we stop all services), just neither redirect nor use '&':
#!/bin/bash # ActiveState Perl export PATH=/home/cavac/bin/ActivePerl-5.12/site/bin:/home/cavac/bin/A +ctivePerl-5.12/bin:$PATH export MANPATH=/home/cavac/bin/ActivePerl-5.12/site/man:/home/cavac/bi +n/ActivePerl-5.12/man:$MANPATH # ActiveState PDK export PATH=/home/cavac/bin/PDK/bin:$PATH export MANPATH=/home/cavac/bin/PDK/share/man:$MANPATH # ActiveState Komodo export PATH=/home/cavac/bin/Komodo-5/bin:$PATH cd /home/cavac/src/maplat_prodit/server perl maplat_svc_linux.pl stop configs/rbssvc.xml
Sorry, this scripts are currently not really documented. If there is a problem, just contact me here on PerlMonks. But so far, the code worked over 2 years without a hitch.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re^3: Problems with forking
by Anonymous Monk on Apr 02, 2011 at 12:28 UTC | |
by cavac (Prior) on Apr 02, 2011 at 12:52 UTC | |
by Anonymous Monk on Apr 02, 2011 at 14:32 UTC |