summaryrefslogtreecommitdiff
path: root/Mailman
diff options
context:
space:
mode:
authorbwarsaw2001-05-02 03:12:28 +0000
committerbwarsaw2001-05-02 03:12:28 +0000
commita57f266f69c566112e50246ed5dfe973625b387c (patch)
tree480fc332a8941e0d9878f5c367758323d484e93b /Mailman
parentc7a495d8944a555cfe88d4153c632c1f79e2ba3e (diff)
downloadmailman-a57f266f69c566112e50246ed5dfe973625b387c.tar.gz
mailman-a57f266f69c566112e50246ed5dfe973625b387c.tar.zst
mailman-a57f266f69c566112e50246ed5dfe973625b387c.zip
Fixes to handle User-Hits-Stop-Button problems, specifically,
main(): Set up a signal handler to catch SIGTERM, and unlock the mailing list when this happens. This has the side effect of aborting any changes to the MailList object that this web hit may have made. This is necessary due to semantics of Apache's mod_cgi: when the browser closes the socket, eventually Apache receives a SIGPIPE (on output to the closed socket). This causes Apache to SIGTERM the cgi process, wait three seconds, then SIGKILL it. We want to be able to clean up the locks, so the best we can do is try to unlock the list on the SIGTERM. Once we get SIGKILLed, there's nothing we can do. This change also moves the Save() call into the try: block so that the finally: block /only/ unlocks the list. Thus, the list gets unlocked in most situations. There are still race conditions where 1) the config.db file could be corrupted; 2) list locks could still be unreleased. Given the semantics of signals in Python, the interaction of Apache's mod_cgi, and other factors, this is the best we can do, and it should be better than the old situation. XXX What do other web servers or cgi execution environments do?
Diffstat (limited to 'Mailman')
-rw-r--r--Mailman/Cgi/admin.py24
-rw-r--r--Mailman/Cgi/admindb.py9
-rw-r--r--Mailman/Cgi/confirm.py9
3 files changed, 38 insertions, 4 deletions
diff --git a/Mailman/Cgi/admin.py b/Mailman/Cgi/admin.py
index 1e4b710b7..95d5558af 100644
--- a/Mailman/Cgi/admin.py
+++ b/Mailman/Cgi/admin.py
@@ -24,6 +24,7 @@ import cgi
import types
import sha
import urllib
+import signal
from string import lowercase, digits
from mimelib.address import unquote
@@ -121,7 +122,7 @@ def main():
# The html page document
doc = Document()
doc.set_language(mlist.preferred_language)
- # Now we're ready to do normal form processing. For this, though we must
+ # Now we're ready to do normal form processing. For this though, we must
# lock the mailing list, and everything from here on out must be wrapped
# in a try/except.
#
@@ -130,8 +131,23 @@ def main():
# or stale lock on the list. Maybe we should have a configurable timeout
# setting after which we'll just inform the user that the operation
# couldn't be performed?
+ #
+ # Set things up so that we can clean up the list lock even if the user
+ # hits the browser's stop button. Note that Apache under mod_cgi
+ # apparently can catch the SIGPIPE that results, and calls SIGTERM on the
+ # CGI process. Python doesn't install a signal handler for SIGTERM, so
+ # that would cause us to summarily exit, leaving list locks laying around
+ # (and this behavior has been confirmed). We can't just ignore SIGTERM
+ # because three seconds later Apache will SIGKILL us, giving us no chance
+ # to exit cleanly. By installing this signal handler, we can catch the
+ # SIGTERM and do the right thing. This may not work under other web
+ # servers, or even other Apache/cgi modules (mod_python, etc.).
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+
mlist.Lock()
try:
+ signal.signal(signal.SIGTERM, sigterm_handler)
if cgidata.keys():
# There are options to change
change_options(mlist, category, cgidata, doc)
@@ -161,8 +177,12 @@ def main():
# Glom up the results page and print it out
show_results(mlist, doc, category, category_suffix, cgidata)
print doc.Format(bgcolor='#ffffff')
- finally:
mlist.Save()
+ finally:
+ # Now be sure to unlock the list. It's okay if we get a signal here
+ # because essentially, the signal handler will do the same thing. And
+ # unlocking is conditional, so it's not an error if we unlock while
+ # we're already unlocked.
mlist.Unlock()
diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
index 65b33e975..cc061be26 100644
--- a/Mailman/Cgi/admindb.py
+++ b/Mailman/Cgi/admindb.py
@@ -20,6 +20,7 @@ import os
import types
import cgi
import errno
+import signal
from mimelib.Parser import Parser
from mimelib.MsgReader import MsgReader
@@ -78,8 +79,14 @@ def main():
# should not need to be locked just to read the request database. However
# the request database asserts that the list is locked in order to load
# it and it's not worth complicating that logic.
+ #
+ # Also, see the comment in admin.py about the need for the signal handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+
mlist.Lock()
try:
+ signal.signal(signal.SIGTERM, sigterm_handler)
realname = mlist.real_name
if not cgidata.keys():
# If this is not a form submission (i.e. there are no keys in the
@@ -91,8 +98,8 @@ def main():
process_form(mlist, doc, cgidata)
# Now print the results and we're done
show_requests(mlist, doc)
- finally:
mlist.Save()
+ finally:
mlist.Unlock()
print doc.Format(bgcolor='#ffffff')
diff --git a/Mailman/Cgi/confirm.py b/Mailman/Cgi/confirm.py
index 2fabebff8..2fee4d930 100644
--- a/Mailman/Cgi/confirm.py
+++ b/Mailman/Cgi/confirm.py
@@ -16,6 +16,8 @@
"""Confirm a pending action via URL."""
+import signal
+
from Mailman import mm_cfg
from Mailman import Errors
from Mailman import i18n
@@ -55,9 +57,14 @@ def main():
i18n.set_language(mlist.preferred_language)
doc.set_language(mlist.preferred_language)
+ # See the comment in admin.py about the need for the signal handler.
+ def sigterm_handler(signum, frame, mlist=mlist):
+ mlist.Unlock()
+
# Now dig out the cookie
mlist.Lock()
try:
+ signal.signal(signal.SIGTERM, sigterm_handler)
try:
cookie = parts[1]
data = mlist.ProcessConfirmation(cookie)
@@ -70,8 +77,8 @@ def main():
please try to re-submit your subscription.'''))
doc.AddItem(mlist.GetMailmanFooter())
print doc.Format(bgcolor='#ffffff')
- finally:
mlist.Save()
+ finally:
mlist.Unlock()