aboutsummaryrefslogtreecommitdiffstats
path: root/bin/qrunner
diff options
context:
space:
mode:
Diffstat (limited to 'bin/qrunner')
-rw-r--r--bin/qrunner270
1 files changed, 270 insertions, 0 deletions
diff --git a/bin/qrunner b/bin/qrunner
new file mode 100644
index 00000000..bb2f62d3
--- /dev/null
+++ b/bin/qrunner
@@ -0,0 +1,270 @@
+#! @PYTHON@
+
+# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Run one or more qrunners, once or repeatedly.
+
+Each named runner class is run in round-robin fashion. In other words, the
+first named runner is run to consume all the files currently in its
+directory. When that qrunner is done, the next one is run to consume all the
+files in /its/ directory, and so on. The number of total iterations can be
+given on the command line.
+
+Usage: %(PROGRAM)s [options]
+
+Options:
+
+ -r runner[:slice:range]
+ --runner=runner[:slice:range]
+ Run the named qrunner, which must be one of the strings returned by
+ the -l option. Optional slice:range if given, is used to assign
+ multiple qrunner processes to a queue. range is the total number of
+ qrunners for this queue while slice is the number of this qrunner from
+ [0..range).
+
+ If using the slice:range form, you better make sure that each qrunner
+ for the queue is given the same range value. If slice:runner is not
+ given, then 1:1 is used.
+
+ Multiple -r options may be given, in which case each qrunner will run
+ once in round-robin fashion. The special runner `All' is shorthand
+ for a qrunner for each listed by the -l option.
+
+ --once
+ -o
+ Run each named qrunner exactly once through its main loop. Otherwise,
+ each qrunner runs indefinitely, until the process receives a SIGTERM
+ or SIGINT.
+
+ -l/--list
+ Shows the available qrunner names and exit.
+
+ -v/--verbose
+ Spit out more debugging information to the logs/qrunner log file.
+
+ -s/--subproc
+ This should only be used when running qrunner as a subprocess of the
+ mailmanctl startup script. It changes some of the exit-on-error
+ behavior to work better with that framework.
+
+ -h/--help
+ Print this message and exit.
+
+runner is required unless -l or -h is given, and it must be one of the names
+displayed by the -l switch.
+"""
+
+import sys
+import getopt
+import signal
+
+import paths
+from Mailman import mm_cfg
+from Mailman.i18n import _
+from Mailman.Logging.Syslog import syslog
+from Mailman.Logging.Utils import LogStdErr
+
+PROGRAM = sys.argv[0]
+COMMASPACE = ', '
+
+# Flag which says whether we're running under mailmanctl or not.
+AS_SUBPROC = 0
+
+LogStdErr('error', 'qrunner', manual_reprime=0)
+
+
+
+def usage(code, msg=''):
+ if code:
+ fd = sys.stderr
+ else:
+ fd = sys.stdout
+ print >> fd, _(__doc__)
+ if msg:
+ print >> fd, msg
+ sys.exit(code)
+
+
+
+def make_qrunner(name, slice, range, once=0):
+ modulename = 'Mailman.Queue.' + name
+ try:
+ __import__(modulename)
+ except ImportError, e:
+ if AS_SUBPROC:
+ # Exit with SIGTERM exit code so mailmanctl won't try to restart us
+ print >> sys.stderr, 'Cannot import runner module', modulename
+ print >> sys.stderr, e
+ sys.exit(signal.SIGTERM)
+ else:
+ usage(1, e)
+ qrclass = getattr(sys.modules[modulename], name)
+ if once:
+ # Subclass to hack in the setting of the stop flag in _doperiodic()
+ class Once(qrclass):
+ def _doperiodic(self):
+ self.stop()
+ qrunner = Once(slice, range)
+ else:
+ qrunner = qrclass(slice, range)
+ return qrunner
+
+
+
+def set_signals(loop):
+ # Set up the SIGTERM handler for stopping the loop
+ def sigterm_handler(signum, frame, loop=loop):
+ # Exit the qrunner cleanly
+ loop.stop()
+ loop.status = signal.SIGTERM
+ syslog('qrunner', '%s qrunner caught SIGTERM. Stopping.', loop.name())
+ signal.signal(signal.SIGTERM, sigterm_handler)
+ # Set up the SIGINT handler for stopping the loop. For us, SIGINT is
+ # the same as SIGTERM, but our parent treats the exit statuses
+ # differently (it restarts a SIGINT but not a SIGTERM).
+ def sigint_handler(signum, frame, loop=loop):
+ # Exit the qrunner cleanly
+ loop.stop()
+ loop.status = signal.SIGINT
+ syslog('qrunner', '%s qrunner caught SIGINT. Stopping.', loop.name())
+ signal.signal(signal.SIGINT, sigint_handler)
+ # SIGHUP just tells us to close our log files. They'll be
+ # automatically reopened at the next log print :)
+ def sighup_handler(signum, frame, loop=loop):
+ syslog.close()
+ syslog('qrunner', '%s qrunner caught SIGHUP. Reopening logs.',
+ loop.name())
+ signal.signal(signal.SIGHUP, sighup_handler)
+
+
+
+def main():
+ global AS_SUBPROC
+ try:
+ opts, args = getopt.getopt(
+ sys.argv[1:], 'hlor:vs',
+ ['help', 'list', 'once', 'runner=', 'verbose', 'subproc'])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ once = 0
+ runners = []
+ verbose = 0
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-l', '--list'):
+ for runnername, slices in mm_cfg.QRUNNERS:
+ if runnername.endswith('Runner'):
+ name = runnername[:-len('Runner')]
+ else:
+ name = runnername
+ print _('%(name)s runs the %(runnername)s qrunner')
+ print _('All runs all the above qrunners')
+ sys.exit(0)
+ elif opt in ('-o', '--once'):
+ once = 1
+ elif opt in ('-r', '--runner'):
+ runnerspec = arg
+ parts = runnerspec.split(':')
+ if len(parts) == 1:
+ runner = parts[0]
+ slice = 1
+ range = 1
+ elif len(parts) == 3:
+ runner = parts[0]
+ try:
+ slice = int(parts[1])
+ range = int(parts[2])
+ except ValueError:
+ usage(1, 'Bad runner specification: %(runnerspec)s')
+ else:
+ usage(1, 'Bad runner specification: %(runnerspec)s')
+ if runner == 'All':
+ for runnername, slices in mm_cfg.QRUNNERS:
+ runners.append((runnername, slice, range))
+ else:
+ if runner.endswith('Runner'):
+ runners.append((runner, slice, range))
+ else:
+ runners.append((runner + 'Runner', slice, range))
+ elif opt in ('-s', '--subproc'):
+ AS_SUBPROC = 1
+ elif opt in ('-v', '--verbose'):
+ verbose = 1
+
+ if len(args) <> 0:
+ usage(1)
+ if len(runners) == 0:
+ usage(1, _('No runner name given.'))
+
+ # Fast track for one infinite runner
+ if len(runners) == 1 and not once:
+ qrunner = make_qrunner(*runners[0])
+ class Loop:
+ status = 0
+ def __init__(self, qrunner):
+ self.__qrunner = qrunner
+ def name(self):
+ return self.__qrunner.__class__.__name__
+ def stop(self):
+ self.__qrunner.stop()
+ loop = Loop(qrunner)
+ set_signals(loop)
+ # Now start up the main loop
+ syslog('qrunner', '%s qrunner started.', loop.name())
+ qrunner.run()
+ syslog('qrunner', '%s qrunner exiting.', loop.name())
+ else:
+ # Anything else we have to handle a bit more specially
+ qrunners = []
+ for runner, slice, range in runners:
+ qrunner = make_qrunner(runner, slice, range, 1)
+ qrunners.append(qrunner)
+ # This class is used to manage the main loop
+ class Loop:
+ status = 0
+ def __init__(self):
+ self.__isdone = 0
+ def name(self):
+ return 'Main loop'
+ def stop(self):
+ self.__isdone = 1
+ def isdone(self):
+ return self.__isdone
+ loop = Loop()
+ set_signals(loop)
+ syslog('qrunner', 'Main qrunner loop started.')
+ while not loop.isdone():
+ for qrunner in qrunners:
+ # In case the SIGTERM came in the middle of this iteration
+ if loop.isdone():
+ break
+ if verbose:
+ syslog('qrunner', 'Now doing a %s qrunner iteration',
+ qrunner.__class__.__bases__[0].__name__)
+ qrunner.run()
+ if once:
+ break
+ syslog('qrunner', 'Main qrunner loop exiting.')
+ # All done
+ sys.exit(loop.status)
+
+
+
+if __name__ == '__main__':
+ main()