diff options
| author | bwarsaw | 2001-05-02 03:12:28 +0000 |
|---|---|---|
| committer | bwarsaw | 2001-05-02 03:12:28 +0000 |
| commit | a57f266f69c566112e50246ed5dfe973625b387c (patch) | |
| tree | 480fc332a8941e0d9878f5c367758323d484e93b /Mailman | |
| parent | c7a495d8944a555cfe88d4153c632c1f79e2ba3e (diff) | |
| download | mailman-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.py | 24 | ||||
| -rw-r--r-- | Mailman/Cgi/admindb.py | 9 | ||||
| -rw-r--r-- | Mailman/Cgi/confirm.py | 9 |
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() |
