summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mailman/bin/docs/mailmanctl.txt27
-rw-r--r--Mailman/bin/master.py3
-rw-r--r--Mailman/tests/helpers.py81
3 files changed, 110 insertions, 1 deletions
diff --git a/Mailman/bin/docs/mailmanctl.txt b/Mailman/bin/docs/mailmanctl.txt
new file mode 100644
index 000000000..bd9023371
--- /dev/null
+++ b/Mailman/bin/docs/mailmanctl.txt
@@ -0,0 +1,27 @@
+Mailman queue runner control
+============================
+
+Mailman has a number of queue runners which process messages in its queue file
+directories. In normal operation, a command line script called 'mailmanctl'
+is used to start, stop and manage the queue runners. mailmanctl actually is
+just a wrapper around the real queue runner watcher script called master.py.
+
+Because master.py runs in the foreground, we can't start it directly, so we'll
+start it via mailmanctl.
+
+ >>> from Mailman.tests.helpers import Watcher
+ >>> watcher = Watcher()
+ >>> watcher.start()
+
+ >>> import os
+
+ # This will raise an exception if the process doesn't exist.
+ >>> os.kill(watcher.pid, 0)
+
+It's also easy to stop the queue runners via the mailmanctl program.
+
+ >>> watcher.stop()
+ >>> os.kill(watcher.pid, 0)
+ Traceback (most recent call last):
+ ...
+ OSError: [Errno ...] No such process
diff --git a/Mailman/bin/master.py b/Mailman/bin/master.py
index e2a80934f..e4de11acc 100644
--- a/Mailman/bin/master.py
+++ b/Mailman/bin/master.py
@@ -350,9 +350,10 @@ qrunner %s reached maximum restart limit of %d, not restarting.""",
# The child has already exited.
log.info('ESRCH on pid: %d', pid)
# Wait for all the children to go away.
- while True:
+ while kids:
try:
pid, status = os.wait()
+ del kids[pid]
except OSError, e:
if e.errno == errno.ECHILD:
break
diff --git a/Mailman/tests/helpers.py b/Mailman/tests/helpers.py
index 180ac8af8..3f35b73dc 100644
--- a/Mailman/tests/helpers.py
+++ b/Mailman/tests/helpers.py
@@ -17,8 +17,11 @@
"""Various test helpers."""
+from __future__ import with_statement
+
__metaclass__ = type
__all__ = [
+ 'Watcher',
'digest_mbox',
'get_queue_messages',
'make_testable_runner',
@@ -26,8 +29,14 @@ __all__ = [
import os
+import time
+import errno
import mailbox
+import subprocess
+
+from datetime import datetime, timedelta
+from Mailman.configuration import config
from Mailman.queue import Switchboard
@@ -82,3 +91,75 @@ def digest_mbox(mlist):
"""
path = os.path.join(mlist.full_path, 'digest.mbox')
return mailbox.mbox(path)
+
+
+
+class Watcher:
+ """A doctest stand-in for the queue file watcher."""
+
+ def __init__(self):
+ self.exe = os.path.join(config.BIN_DIR, 'mailmanctl')
+ self.returncode = None
+ self.stdout = None
+ self.stderr = None
+ self.pid = None
+
+ def start(self):
+ """Start the watcher and wait until it actually starts."""
+ process = subprocess.Popen(
+ (self.exe, '-C', config.filename, '-q', 'start'))
+ stdout, stderr = process.communicate()
+ # Wait until the pid file exists.
+ until = datetime.now() + timedelta(seconds=2)
+ while datetime.now() < until:
+ try:
+ with open(config.PIDFILE) as f:
+ pid = int(f.read().strip())
+ break
+ except IOError, error:
+ if error.errno == errno.ENOENT:
+ time.sleep(0.1)
+ else:
+ raise
+ else:
+ # This will usually cause the doctest to fail.
+ return 'Time out'
+ # Now wait until the process actually exists.
+ until = datetime.now() + timedelta(seconds=2)
+ while datetime.now() < until:
+ try:
+ os.kill(pid, 0)
+ break
+ except OSError, error:
+ if error.errno == errno.ESRCH:
+ time.sleep(0.1)
+ else:
+ raise
+ else:
+ return 'Time out'
+ self.returncode = process.returncode
+ self.stdout = stdout
+ self.stderr = stderr
+ self.pid = pid
+
+ def stop(self):
+ """Stop the watcher and wait until it actually stops."""
+ process = subprocess.Popen(
+ (self.exe, '-C', config.filename, '-q', 'stop'))
+ stdout, stderr = process.communicate()
+ # Now wait until the process stops.
+ until = datetime.now() + timedelta(seconds=2)
+ while datetime.now() < until:
+ try:
+ os.kill(self.pid, 0)
+ time.sleep(0.1)
+ except OSError, error:
+ if error.errno == errno.ESRCH:
+ break
+ else:
+ raise
+ else:
+ return 'Time out'
+ self.returncode = process.returncode
+ self.stdout = stdout
+ self.stderr = stderr