summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/core/runner.py15
-rw-r--r--src/mailman/docs/NEWS.rst2
-rw-r--r--src/mailman/interfaces/runner.py13
-rw-r--r--src/mailman/runners/lmtp.py11
-rw-r--r--src/mailman/runners/rest.py8
5 files changed, 41 insertions, 8 deletions
diff --git a/src/mailman/core/runner.py b/src/mailman/core/runner.py
index 757d8837e..9f8094153 100644
--- a/src/mailman/core/runner.py
+++ b/src/mailman/core/runner.py
@@ -31,7 +31,8 @@ from mailman.core.logging import reopen
from mailman.core.switchboard import Switchboard
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.listmanager import IListManager
-from mailman.interfaces.runner import IRunner, RunnerCrashEvent
+from mailman.interfaces.runner import (
+ IRunner, RunnerCrashEvent, RunnerInterrupt)
from mailman.utilities.string import expand
from public import public
from zope.component import getUtility
@@ -98,6 +99,16 @@ class Runner:
elif signum == signal.SIGHUP:
reopen()
rlog.info('%s runner caught SIGHUP. Reopening logs.', self.name)
+ # As of Python 3.5, PEP 475 gets in our way. Runners with long
+ # time.sleep()'s in their _snooze() method (e.g. the retry runner) will
+ # have their system call implemented time.sleep() automatically retried
+ # at the C layer. The only reliable way to prevent this is to raise an
+ # exception in the signal handler. The standard run() method
+ # automatically suppresses this exception, meaning, it's caught and
+ # ignored, but effectively breaks the run() loop, which is just what we
+ # want. Runners which implement their own run() method must be
+ # prepared to catch RunnerInterrupts, usually also ignoring them.
+ raise RunnerInterrupt
def set_signals(self):
"""See `IRunner`."""
@@ -113,7 +124,7 @@ class Runner:
def run(self):
"""See `IRunner`."""
# Start the main loop for this runner.
- with suppress(KeyboardInterrupt):
+ with suppress(KeyboardInterrupt, RunnerInterrupt):
while True:
# Once through the loop that processes all the files in the
# queue directory.
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 5736c1354..dfe71755f 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -106,6 +106,8 @@ Bugs
* Messages were shunted when non-ASCII characters appeared in a mailing
list's description. Given by Mark Sapiro. (Closes: #215)
* Fix confirmation of unsubscription requests. (Closes: #294)
+ * Fix ``mailman stop`` not stopping some runners due to PEP 475 interaction.
+ (Closes: #255)
Configuration
-------------
diff --git a/src/mailman/interfaces/runner.py b/src/mailman/interfaces/runner.py
index 12a7e49ad..f85c759c1 100644
--- a/src/mailman/interfaces/runner.py
+++ b/src/mailman/interfaces/runner.py
@@ -34,6 +34,19 @@ class RunnerCrashEvent:
@public
+class RunnerInterrupt(Exception):
+ """A runner received a system call interrupting signal.
+
+ PEP 475 automatically, and at the C layer, retries system calls such as
+ time.sleep(). This can mean runners with long sleeps in their _snooze()
+ method won't actually exit. This exception is always raised in Mailman's
+ runner signal handlers to prevent this behavior. Runners that implement
+ their own .run() method must be prepared to handle this, usually by
+ ignoring it.
+ """
+
+
+@public
class IRunner(Interface):
"""The runner."""
diff --git a/src/mailman/runners/lmtp.py b/src/mailman/runners/lmtp.py
index b5c7bbb1f..f24107bd7 100644
--- a/src/mailman/runners/lmtp.py
+++ b/src/mailman/runners/lmtp.py
@@ -40,12 +40,14 @@ import logging
from aiosmtpd.controller import Controller
from aiosmtpd.lmtp import LMTP
+from contextlib import suppress
from email.utils import parseaddr
from mailman.config import config
from mailman.core.runner import Runner
from mailman.database.transaction import transactional
from mailman.email.message import Message
from mailman.interfaces.listmanager import IListManager
+from mailman.interfaces.runner import RunnerInterrupt
from mailman.utilities.datetime import now
from mailman.utilities.email import add_message_hash
from public import public
@@ -240,7 +242,8 @@ class LMTPRunner(Runner):
def run(self):
"""See `IRunner`."""
- self.lmtp.start()
- while not self._stop:
- self._snooze(0)
- self.lmtp.stop()
+ with suppress(RunnerInterrupt):
+ self.lmtp.start()
+ while not self._stop:
+ self._snooze(0)
+ self.lmtp.stop()
diff --git a/src/mailman/runners/rest.py b/src/mailman/runners/rest.py
index a93d164c1..7758972ed 100644
--- a/src/mailman/runners/rest.py
+++ b/src/mailman/runners/rest.py
@@ -21,7 +21,9 @@ import signal
import logging
import threading
+from contextlib import suppress
from mailman.core.runner import Runner
+from mailman.interfaces.runner import RunnerInterrupt
from mailman.rest.wsgiapp import make_server
from public import public
@@ -59,10 +61,12 @@ class RESTRunner(Runner):
def run(self):
"""See `IRunner`."""
- self._server.serve_forever()
+ with suppress(RunnerInterrupt):
+ self._server.serve_forever()
def signal_handler(self, signum, frame):
- super().signal_handler(signum, frame)
+ with suppress(RunnerInterrupt):
+ super().signal_handler(signum, frame)
if signum in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1):
# Set the flag that will terminate the TCPserver loop.
self._event.set()