http://www.perlmonks.org?node_id=799688

chanakya has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks

I have a bunch of scripts which monitor incoming email, process them and load parsed data into Mysql.
I'm demonizing the scripts, in order to process the incoming emails at a faster rate. I have implemented
Proc::Daemon to achieve this. All is well till now.

I'm planning to add modes (like normal mode or daemon mode) to the script, so that I can execute
scripts in daemon mode via the cron, and use the normal mode if I want to troubleshoot or manually
load some emails.

In my code below I have added the daemon mode via the command line options.
Here I need your suggestion on how can i simplify the code and also remove duplicated code. The code shown is a sample script, and the code for some of the scripts is long.

#!/usr/bin/perl use strict; use warnings; use Getopt::Long; use Proc::Daemon; use Log::Log4perl qw(get_logger :easy :levels); Use MysqlUtils; use vars qw($opt_daemon, $opt_mailid); GetOptions( "daemon|d" => \$opt_daemon, "mailid|a=s" => \$opt_mailid, "help|h" => \&usage, ) || usage(); if(!$opt_mailid){ $opt_mailid = shift || usage(); } chomp($opt_mailid); &usage if( (!defined($opt_mailid)) && (@ARGV==0) ); if(scalar(@ARGV) > 0){ if ( (!defined ($opt_mailid)) || (@ARGV > 0) ) { usage(); die("Invalid Argument passed"); } } if($opt_daemon) { ##Inits print STDOUT "Daemon mode \n"; Proc::Daemon::Init; Log::Log4perl->init_once("$ENV{CONF}" . "/log4perl.conf"); my @mail_ids = qw(200909101 200909102 200909103); while (1) { foreach my $opt_mailid (@mail_ids) { my ($log) = Log::Log4perl::get_logger('main'); my $data = get_data('mail_id' => $opt_mailid, 'file_path' => " +/spool/emails"); ##connect db MysqlUtils::connect(); eval { MysqlUtils::insert_record($data); }; if($@) { $log->error("unable to insert data"); } } } } else { print STDOUT "Normal mode\n"; my ($log) = Log::Log4perl::get_logger('main'); my $data = get_data('mail_id' => $opt_mailid, 'file_path' => "/spo +ol/emails"); ##connect db MysqlUtils::connect(); eval { MysqlUtils::insert_record($data); }; if($@) { $log->error("unable to insert data"); } }
The above code works perfectly in the daemon mode and normal mode.
Here's my two questions and seeking suggestions to improve the code

* How can avoid code duplication
* How can I add start/stop commands for the script. thank you for your time
  • Comment on Executing script in normal/daemon mode and adding start/stop capability
  • Download Code

Replies are listed 'Best First'.
Re: Executing script in normal/daemon mode and adding start/stop capability
by almut (Canon) on Oct 07, 2009 at 12:35 UTC
    * How can I add start/stop commands for the script.

    One way to do it would be to add the respective options to your script (as you're already using Getopt::Long, this should be rather straightforward).  The implementation would typically involve a pid-file to store the PID which you need to communicate with a potentially running instance of the script. Something like:

    • start

      • Check if there's a pid-file.
      • If so, read the PID from the file, and send that PID signal number zero to check if it's still alive.
      • If it is alive, output "... already running", and quit.
      • In case no running instance is found, start the daemon the same way you would do from the command line. When successful, create a pid-file containing the PID of the process.

    • stop

      • Check if there's a pid-file.
      • If there is no pid-file, output "... doesn't seem to be running", and quit.
      • Otherwise, read the PID from the file, and send that PID the TERM signal (or whatever signal makes your script quit gracefully).
      • Delete the pid-file. (In case you're paranoid, you might want to wait a while and then check whether the TERM signal did actually terminate the process, before deleting the pid-file. If not, output an error message.)

Re: Executing script in normal/daemon mode and adding start/stop capability
by ELISHEVA (Prior) on Oct 07, 2009 at 11:12 UTC

    To avoid code duplication, use a subroutine.

    Also you will find your code easier to read an debug if you use consistent indenting. The block of code controlled by a flow-of-control statement should always be indented inside the flow of control statement. It is also a good idea to be consistent in your choice of cuddled and uncuddled curly braces.

    A third bit of advice, $@ can sometimes be set to undef even if there is an error. Even so, eval will always return undefined when there is an error. A much better way to check for errors is the incantation:

    eval { # my stuff ... #Note: return *only* exits eval {...} #make sure success "returns" true return 1; } or do { # what you are currently putting inside if ($@) {...} goes here. }

    With a subroutine and the above eval incantation, your code (indenting revised):

    becomes

    sub runMailHandler { my ($opt_mailid) = @_; my ($log) = Log::Log4perl::get_logger('main'); my $data = get_data('mail_id' => $opt_mailid , 'file_path' => "/spool/emails"); ##connect db MysqlUtils::connect(); eval { MysqlUtils::insert_record($data); return 1; } or do { $log->error("unable to insert data"); } } if($opt_daemon) { # intro stuff ... while (1) { runMailHandler($_) foreach @mail_ids; } } else { runMailHandler($opt_mailid); }

    Best, beth

    Update Added comments about eval handling.

Re: Executing script in normal/daemon mode and adding start/stop capability
by afoken (Chancellor) on Oct 07, 2009 at 22:23 UTC

    I prefer daemontools for background processes, as explained in Re: keep a module in shared memory. No need to add a single line of code to your script, no race conditions when handling PID files (because daemontools do not need PID files), and a simple control mechanism. Just write a char to a pipe or let the svc utility do that for you.

    (And yes, djb's behaviour regarding errno is just silly and childish. Apply the patch from thedjbway.org or complain loudly to djb that he does not want to accept the C standards. And by the way, also apply the other patch that adds support for SIGQUIT, SIGUSR1, and SIGUSR2.)

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: Executing script in normal/daemon mode and adding start/stop capability
by chanakya (Friar) on Oct 08, 2009 at 06:47 UTC
    afoken, almut and Elisheva thank you for your suggestions on various solutions to address the issues.

    I'll check which one is best suited for my situation.
    Cheers