auto-use export classes
[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     #auto-use export classes...
104     if ( $ljob->job =~ /(FS::part_export::\w+)::/ ) {
105       my $class = $1;
106       eval "use $class;";
107       if ( $@ ) {
108         warn "job use $class failed";
109         my %hash = $ljob->hash;
110         $hash{'status'} = 'failed';
111         $hash{'statustext'} = $@;
112         my $fjob = new FS::queue( \%hash );
113         my $error = $fjob->replace($ljob);
114         die $error if $error;
115         exit; #end-of-kid
116       };
117     }
118
119     my $eval = "&". $ljob->job. '(@args);';
120     warn "running $eval";
121     eval $eval; #throw away return value?  suppose so
122     if ( $@ ) {
123       warn "job $eval failed";
124       my %hash = $ljob->hash;
125       $hash{'status'} = 'failed';
126       $hash{'statustext'} = $@;
127       my $fjob = new FS::queue( \%hash );
128       my $error = $fjob->replace($ljob);
129       die $error if $error;
130     } else {
131       $ljob->delete;
132     }
133
134     exit;
135     #end-of-kid
136   }
137
138 } continue {
139   if ( $sigterm ) {
140     warn "received TERM signal; exiting\n";
141     exit;
142   }
143   if ( $sigint ) {
144     warn "received INT signal; exiting\n";
145     exit;
146   }
147 }
148
149 sub usage {
150   die "Usage:\n\n  freeside-queued user\n";
151 }
152
153 sub _die {
154   my $msg = shift;
155   unlink $pid_file if -e $pid_file;
156   _logmsg($msg);
157 }
158
159 sub _logmsg {
160   chomp( my $msg = shift );
161   my $log = new IO::File ">>$log_file";
162   flock($log, LOCK_EX);
163   seek($log, 0, 2);
164   print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
165   flock($log, LOCK_UN);
166   close $log;
167 }
168
169 sub daemonize1 {
170
171   chdir "/" or die "Can't chdir to /: $!";
172   open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
173   defined(my $pid = fork) or die "Can't fork: $!";
174   if ( $pid ) {
175     print "freeside-queued started with pid $pid\n"; #logging to $log_file\n";
176     exit unless $pid_file;
177     my $pidfh = new IO::File ">$pid_file" or exit;
178     print $pidfh "$pid\n";
179     exit;
180   }
181   #open STDOUT, '>/dev/null'
182   #                          or die "Can't write to /dev/null: $!";
183   #setsid                  or die "Can't start a new session: $!";
184   #open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
185
186 }
187
188 sub daemonize2 {
189   open STDOUT, '>/dev/null'
190                             or die "Can't write to /dev/null: $!";
191   setsid                  or die "Can't start a new session: $!";
192   open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
193 }
194
195 =head1 NAME
196
197 freeside-queued - Job queue daemon
198
199 =head1 SYNOPSIS
200
201   freeside-queued user
202
203 =head1 DESCRIPTION
204
205 Job queue daemon.  Should be running at all times.
206
207 user: from the mapsecrets file - see config.html from the base documentation
208
209 =head1 VERSION
210
211 =head1 BUGS
212
213 =head1 SEE ALSO
214
215 =cut
216