f6226cca14e6647be7fd7f6cc2cc7b0401bd1001
[freeside.git] / FS / bin / freeside-queued
1 #!/usr/bin/perl -w
2
3 use strict;
4 use vars qw( $log_file $sigterm $sigint $kids $max_kids );
5 use subs qw( _die _logmsg );
6 use Fcntl qw(:flock);
7 use POSIX qw(setsid);
8 use Date::Format;
9 use IO::File;
10 use FS::UID qw(adminsuidsetup forksuidsetup driver_name);
11 use FS::Record qw(qsearchs);
12 use FS::queue;
13
14 # no autoloading just yet
15 use FS::cust_main;
16 use FS::svc_acct;
17 use Net::SSH 0.05;
18 use FS::part_export;
19
20 my $pid_file = '/var/run/freeside-queued.pid';
21
22 $max_kids = '10'; #guess it should be a config file...
23 $kids = 0;
24
25 my $user = shift or die &usage;
26
27 &daemonize1;
28
29 sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
30 $SIG{CHLD} =  \&REAPER;
31
32 $sigterm = 0;
33 $sigint = 0;
34 $SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; };
35 $SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; };
36
37 $> = $FS::UID::freeside_uid unless $>;
38 $< = $>;
39 $ENV{HOME} = (getpwuid($>))[7]; #for ssh
40 adminsuidsetup $user;
41
42 $log_file = "/usr/local/etc/freeside/queuelog.". $FS::UID::datasrc;
43
44 &daemonize2;
45
46 $SIG{__DIE__} = \&_die;
47 $SIG{__WARN__} = \&_logmsg;
48
49 warn "freeside-queued starting\n";
50
51 my $warnkids=0;
52 while (1) {
53
54   #prevent runaway forking
55   if ( $kids >= $max_kids ) {
56     warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
57     sleep 1; #waiting for signals is cheap
58     next;
59   }
60   $warnkids=0;
61
62   my $job = qsearchs(
63     'queue',
64     { 'status' => 'new' },
65     '',
66     driver_name =~ /^mysql$/i
67       ? 'ORDER BY jobnum LIMIT 1 FOR UPDATE'
68       : 'ORDER BY jobnum FOR UPDATE LIMIT 1'
69   ) or do {
70     sleep 5; #connecting to db is expensive
71     next;
72   };
73
74   my %hash = $job->hash;
75   $hash{'status'} = 'locked';
76   my $ljob = new FS::queue ( \%hash );
77   my $error = $ljob->replace($job);
78   die $error if $error;
79
80   my @args = $ljob->args;
81
82   defined( my $pid = fork ) or do {
83     warn "WARNING: can't fork: $!\n";
84     my %hash = $job->hash;
85     $hash{'status'} = 'failed';
86     $hash{'statustext'} = "[freeside-queued] can't fork: $!";
87     my $ljob = new FS::queue ( \%hash );
88     my $error = $ljob->replace($job);
89     die $error if $error;
90     next; #don't increment the kid counter
91   };
92
93   if ( $pid ) {
94     $kids++;
95   } else { #kid time
96
97     #get new db handles
98     $FS::UID::dbh->{InactiveDestroy} = 1;
99     $FS::svc_acct::icradius_dbh->{InactiveDestroy} = 1
100       if $FS::svc_acct::icradius_dbh;
101     forksuidsetup($user);
102
103     my $eval = "&". $ljob->job. '(@args);';
104     warn "running $eval";
105     eval $eval; #throw away return value?  suppose so
106     if ( $@ ) {
107       warn "job $eval failed";
108       my %hash = $ljob->hash;
109       $hash{'status'} = 'failed';
110       $hash{'statustext'} = $@;
111       my $fjob = new FS::queue( \%hash );
112       my $error = $fjob->replace($ljob);
113       die $error if $error;
114     } else {
115       $ljob->delete;
116     }
117
118     exit;
119     #end-of-kid
120   }
121
122 } continue {
123   if ( $sigterm ) {
124     warn "received TERM signal; exiting\n";
125     exit;
126   }
127   if ( $sigint ) {
128     warn "received INT signal; exiting\n";
129     exit;
130   }
131 }
132
133 sub usage {
134   die "Usage:\n\n  freeside-queued user\n";
135 }
136
137 sub _die {
138   my $msg = shift;
139   unlink $pid_file if -e $pid_file;
140   _logmsg($msg);
141 }
142
143 sub _logmsg {
144   chomp( my $msg = shift );
145   my $log = new IO::File ">>$log_file";
146   flock($log, LOCK_EX);
147   seek($log, 0, 2);
148   print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
149   flock($log, LOCK_UN);
150   close $log;
151 }
152
153 sub daemonize1 {
154
155   chdir "/" or die "Can't chdir to /: $!";
156   open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
157   defined(my $pid = fork) or die "Can't fork: $!";
158   if ( $pid ) {
159     print "freeside-queued started with pid $pid\n"; #logging to $log_file\n";
160     exit unless $pid_file;
161     my $pidfh = new IO::File ">$pid_file" or exit;
162     print $pidfh "$pid\n";
163     exit;
164   }
165   #open STDOUT, '>/dev/null'
166   #                          or die "Can't write to /dev/null: $!";
167   #setsid                  or die "Can't start a new session: $!";
168   #open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
169
170 }
171
172 sub daemonize2 {
173   open STDOUT, '>/dev/null'
174                             or die "Can't write to /dev/null: $!";
175   setsid                  or die "Can't start a new session: $!";
176   open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
177 }
178
179 =head1 NAME
180
181 freeside-queued - Job queue daemon
182
183 =head1 SYNOPSIS
184
185   freeside-queued user
186
187 =head1 DESCRIPTION
188
189 Job queue daemon.  Should be running at all times.
190
191 user: from the mapsecrets file - see config.html from the base documentation
192
193 =head1 VERSION
194
195 =head1 BUGS
196
197 =head1 SEE ALSO
198
199 =cut
200