Apply 79_archiver_slash.patch
[mspang/vmailman.git] / bin / qrunner
1 #! @PYTHON@
2
3 # Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 # USA.
19
20 """Run one or more qrunners, once or repeatedly.
21
22 Each named runner class is run in round-robin fashion.  In other words, the
23 first named runner is run to consume all the files currently in its
24 directory.  When that qrunner is done, the next one is run to consume all the
25 files in /its/ directory, and so on.  The number of total iterations can be
26 given on the command line.
27
28 Usage: %(PROGRAM)s [options]
29
30 Options:
31
32     -r runner[:slice:range]
33     --runner=runner[:slice:range]
34         Run the named qrunner, which must be one of the strings returned by
35         the -l option.  Optional slice:range if given, is used to assign
36         multiple qrunner processes to a queue.  range is the total number of
37         qrunners for this queue while slice is the number of this qrunner from
38         [0..range).
39
40         If using the slice:range form, you better make sure that each qrunner
41         for the queue is given the same range value.  If slice:runner is not
42         given, then 1:1 is used.
43
44         Multiple -r options may be given, in which case each qrunner will run
45         once in round-robin fashion.  The special runner `All' is shorthand
46         for a qrunner for each listed by the -l option.
47
48     --once
49     -o
50         Run each named qrunner exactly once through its main loop.  Otherwise,
51         each qrunner runs indefinitely, until the process receives a SIGTERM
52         or SIGINT.
53
54     -l/--list
55         Shows the available qrunner names and exit.
56
57     -v/--verbose
58         Spit out more debugging information to the logs/qrunner log file.
59
60     -s/--subproc
61         This should only be used when running qrunner as a subprocess of the
62         mailmanctl startup script.  It changes some of the exit-on-error
63         behavior to work better with that framework.
64
65     -h/--help
66         Print this message and exit.
67
68 runner is required unless -l or -h is given, and it must be one of the names
69 displayed by the -l switch.
70
71 Note also that this script should be started up from mailmanctl as a normal
72 operation.  It is only useful for debugging if it is run separately.
73 """
74
75 import sys
76 import getopt
77 import signal
78
79 import paths
80 from Mailman import mm_cfg
81 from Mailman.i18n import _
82 from Mailman.Logging.Syslog import syslog
83 from Mailman.Logging.Utils import LogStdErr
84
85 PROGRAM = sys.argv[0]
86 COMMASPACE = ', '
87
88 # Flag which says whether we're running under mailmanctl or not.
89 AS_SUBPROC = 0
90
91
92 \f
93 def usage(code, msg=''):
94     if code:
95         fd = sys.stderr
96     else:
97         fd = sys.stdout
98     print >> fd, _(__doc__)
99     if msg:
100         print >> fd, msg
101     sys.exit(code)
102
103
104 \f
105 def make_qrunner(name, slice, range, once=0):
106     modulename = 'Mailman.Queue.' + name
107     try:
108         __import__(modulename)
109     except ImportError, e:
110         if AS_SUBPROC:
111             # Exit with SIGTERM exit code so mailmanctl won't try to restart us
112             print >> sys.stderr, 'Cannot import runner module', modulename
113             print >> sys.stderr, e
114             sys.exit(signal.SIGTERM)
115         else:
116             usage(1, e)
117     qrclass = getattr(sys.modules[modulename], name)
118     if once:
119         # Subclass to hack in the setting of the stop flag in _doperiodic()
120         class Once(qrclass):
121             def _doperiodic(self):
122                 self.stop()
123         qrunner = Once(slice, range)
124     else:
125         qrunner = qrclass(slice, range)
126     return qrunner
127
128
129 \f
130 def set_signals(loop):
131     # Set up the SIGTERM handler for stopping the loop
132     def sigterm_handler(signum, frame, loop=loop):
133         # Exit the qrunner cleanly
134         loop.stop()
135         loop.status = signal.SIGTERM
136         syslog('qrunner', '%s qrunner caught SIGTERM.  Stopping.', loop.name())
137     signal.signal(signal.SIGTERM, sigterm_handler)
138     # Set up the SIGINT handler for stopping the loop.  For us, SIGINT is
139     # the same as SIGTERM, but our parent treats the exit statuses
140     # differently (it restarts a SIGINT but not a SIGTERM).
141     def sigint_handler(signum, frame, loop=loop):
142         # Exit the qrunner cleanly
143         loop.stop()
144         loop.status = signal.SIGINT
145         syslog('qrunner', '%s qrunner caught SIGINT.  Stopping.', loop.name())
146     signal.signal(signal.SIGINT, sigint_handler)
147     # SIGHUP just tells us to close our log files.  They'll be
148     # automatically reopened at the next log print :)
149     def sighup_handler(signum, frame, loop=loop):
150         syslog.close()
151         syslog('qrunner', '%s qrunner caught SIGHUP.  Reopening logs.',
152                loop.name())
153     signal.signal(signal.SIGHUP, sighup_handler)
154
155
156 \f
157 def main():
158     global AS_SUBPROC
159     try:
160         opts, args = getopt.getopt(
161             sys.argv[1:], 'hlor:vs',
162             ['help', 'list', 'once', 'runner=', 'verbose', 'subproc'])
163     except getopt.error, msg:
164         usage(1, msg)
165
166     once = 0
167     runners = []
168     verbose = 0
169     for opt, arg in opts:
170         if opt in ('-h', '--help'):
171             usage(0)
172         elif opt in ('-l', '--list'):
173             for runnername, slices in mm_cfg.QRUNNERS:
174                 if runnername.endswith('Runner'):
175                     name = runnername[:-len('Runner')]
176                 else:
177                     name = runnername
178                 print _('%(name)s runs the %(runnername)s qrunner')
179             print _('All runs all the above qrunners')
180             sys.exit(0)
181         elif opt in ('-o', '--once'):
182             once = 1
183         elif opt in ('-r', '--runner'):
184             runnerspec = arg
185             parts = runnerspec.split(':')
186             if len(parts) == 1:
187                 runner = parts[0]
188                 slice = 1
189                 range = 1
190             elif len(parts) == 3:
191                 runner = parts[0]
192                 try:
193                     slice = int(parts[1])
194                     range = int(parts[2])
195                 except ValueError:
196                     usage(1, 'Bad runner specification: %(runnerspec)s')
197             else:
198                 usage(1, 'Bad runner specification: %(runnerspec)s')
199             if runner == 'All':
200                 for runnername, slices in mm_cfg.QRUNNERS:
201                     runners.append((runnername, slice, range))
202             else:
203                 if runner.endswith('Runner'):
204                     runners.append((runner, slice, range))
205                 else:
206                     runners.append((runner + 'Runner', slice, range))
207         elif opt in ('-s', '--subproc'):
208             AS_SUBPROC = 1
209         elif opt in ('-v', '--verbose'):
210             verbose = 1
211
212     if len(args) <> 0:
213         usage(1)
214     if len(runners) == 0:
215         usage(1, _('No runner name given.'))
216
217     # Before we startup qrunners, we redirect the stderr to mailman syslog.
218     # We assume !AS_SUBPROC is running for debugging purpose and don't
219     # log errors in mailman logs/error but keep printing to stderr.
220     if AS_SUBPROC:
221         LogStdErr('error', 'qrunner', manual_reprime=0, tee_to_real_stderr=0)
222
223     # Fast track for one infinite runner
224     if len(runners) == 1 and not once:
225         qrunner = make_qrunner(*runners[0])
226         class Loop:
227             status = 0
228             def __init__(self, qrunner):
229                 self.__qrunner = qrunner
230             def name(self):
231                 return self.__qrunner.__class__.__name__
232             def stop(self):
233                 self.__qrunner.stop()
234         loop = Loop(qrunner)
235         set_signals(loop)
236         # Now start up the main loop
237         syslog('qrunner', '%s qrunner started.', loop.name())
238         qrunner.run()
239         syslog('qrunner', '%s qrunner exiting.', loop.name())
240     else:
241         # Anything else we have to handle a bit more specially
242         qrunners = []
243         for runner, slice, range in runners:
244             qrunner = make_qrunner(runner, slice, range, 1)
245             qrunners.append(qrunner)
246         # This class is used to manage the main loop
247         class Loop:
248             status = 0
249             def __init__(self):
250                 self.__isdone = 0
251             def name(self):
252                 return 'Main loop'
253             def stop(self):
254                 self.__isdone = 1
255             def isdone(self):
256                 return self.__isdone
257         loop = Loop()
258         set_signals(loop)
259         syslog('qrunner', 'Main qrunner loop started.')
260         while not loop.isdone():
261             for qrunner in qrunners:
262                 # In case the SIGTERM came in the middle of this iteration
263                 if loop.isdone():
264                     break
265                 if verbose:
266                     syslog('qrunner', 'Now doing a %s qrunner iteration',
267                            qrunner.__class__.__bases__[0].__name__)
268                 qrunner.run()
269             if once:
270                 break
271         syslog('qrunner', 'Main qrunner loop exiting.')
272     # All done
273     sys.exit(loop.status)
274
275
276 \f
277 if __name__ == '__main__':
278     main()