Index: NEWS =================================================================== RCS file: /cvsroot/mailman/mailman/NEWS,v retrieving revision 1.25.2.5 retrieving revision 1.25.2.6 diff -u -r1.25.2.5 -r1.25.2.6 --- NEWS 2001/04/18 10:45:54 1.25.2.5 +++ NEWS 2001/05/03 21:06:56 1.25.2.6 @@ -4,6 +4,13 @@ Here is a history of user visible changes to Mailman. +2.0.5 (04-May-2001) + + Fix a lock stagnation problem that can result when the user hits + the `stop' button on their browser during a write operation that + can take a long time (e.g. hitting the membership management admin + page). + 2.0.4 (18-Apr-2001) Python 2.1 compatibility release. There were a few questionable Index: Mailman/Version.py =================================================================== RCS file: /cvsroot/mailman/mailman/Mailman/Version.py,v retrieving revision 1.20.2.4 retrieving revision 1.20.2.5 diff -u -r1.20.2.4 -r1.20.2.5 --- Mailman/Version.py 2001/04/18 04:43:29 1.20.2.4 +++ Mailman/Version.py 2001/05/03 20:58:19 1.20.2.5 @@ -15,7 +15,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Mailman version -VERSION = "2.0.4" +VERSION = "2.0.5" # And as a hex number in the manner of PY_VERSION_HEX ALPHA = 0xa @@ -27,7 +27,7 @@ MAJOR_REV = 2 MINOR_REV = 0 -MICRO_REV = 4 +MICRO_REV = 5 REL_LEVEL = FINAL # at most 15 beta releases! REL_SERIAL = 0 Index: Mailman/Cgi/admin.py =================================================================== RCS file: /cvsroot/mailman/mailman/Mailman/Cgi/admin.py,v retrieving revision 1.82.2.2 retrieving revision 1.82.2.3 diff -u -r1.82.2.2 -r1.82.2.3 --- Mailman/Cgi/admin.py 2001/01/03 16:47:47 1.82.2.2 +++ Mailman/Cgi/admin.py 2001/05/03 21:03:48 1.82.2.3 @@ -18,11 +18,13 @@ """ +import sys import os import cgi import string import types import rfc822 +import signal from Mailman import Utils from Mailman import MailList @@ -63,53 +65,86 @@ # get the list object listname = string.lower(parts[0]) try: - mlist = MailList.MailList(listname) + mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: FormatAdminOverview('No such list %s' % listname) syslog('error', 'Someone tried to access the admin interface for a ' 'non-existent list: %s' % listname) return + + if len(parts) == 1: + category = 'general' + category_suffix = '' + else: + category = parts[1] + category_suffix = category + + # If the user is not authenticated, we're done. + cgidata = cgi.FieldStorage(keep_blank_values=1) try: - if len(parts) == 1: - category = 'general' - category_suffix = '' - else: - category = parts[1] - category_suffix = category - - # If the user is not authenticated, we're done. - cgidata = cgi.FieldStorage(keep_blank_values=1) - try: - Auth.authenticate(mlist, cgidata) - except Auth.NotLoggedInError, e: - Auth.loginpage(mlist, 'admin', e.message) - return - - # Is this a log-out request? - if category == 'logout': - print mlist.ZapCookie('admin') - Auth.loginpage(mlist, 'admin', frontpage=1) - return - - if category not in map(lambda x: x[0], CATEGORIES): - category = 'general' - - # is the request for variable details? - varhelp = None - if cgidata.has_key('VARHELP'): - varhelp = cgidata['VARHELP'].value - elif cgidata.has_key('request_login') and \ - os.environ.get('QUERY_STRING'): - # POST methods, even if their actions have a query string, don't - # get put into FieldStorage's keys :-( - qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') - if qs and type(qs) == types.ListType: - varhelp = qs[0] - if varhelp: - FormatOptionHelp(doc, varhelp, mlist) - print doc.Format(bgcolor="#ffffff") - return + Auth.authenticate(mlist, cgidata) + except Auth.NotLoggedInError, e: + Auth.loginpage(mlist, 'admin', e.message) + return + + # Is this a log-out request? + if category == 'logout': + print mlist.ZapCookie('admin') + Auth.loginpage(mlist, 'admin', frontpage=1) + return + + if category not in map(lambda x: x[0], CATEGORIES): + category = 'general' + # is the request for variable details? + varhelp = None + if cgidata.has_key('VARHELP'): + varhelp = cgidata['VARHELP'].value + elif cgidata.has_key('request_login') and \ + os.environ.get('QUERY_STRING'): + # POST methods, even if their actions have a query string, don't + # get put into FieldStorage's keys :-( + qs = cgi.parse_qs(os.environ['QUERY_STRING']).get('VARHELP') + if qs and type(qs) == types.ListType: + varhelp = qs[0] + if varhelp: + FormatOptionHelp(doc, varhelp, mlist) + print doc.Format(bgcolor="#ffffff") + return + + # From this point on, the MailList object must be locked. However, we + # must release the lock no matter how we exit. try/finally isn't + # enough, because of this scenario: user hits the admin page which may + # take a long time to render; user gets bored and hits the browser's + # STOP button; browser shuts down socket; server tries to write to + # broken socket and gets a SIGPIPE. Under Apache 1.3/mod_cgi, Apache + # catches this SIGPIPE (I presume it is buffering output from the cgi + # script), then turns around and SIGTERMs the cgi process. Apache + # waits three seconds and then SIGKILLs the cgi process. We /must/ + # catch the SIGTERM and do the most reasonable thing we can in as + # short a time period as possible. If we get the SIGKILL we're + # screwed (because its uncatchable and we'll have no opportunity to + # clean up after ourselves). + # + # This signal handler catches the SIGTERM and unlocks the list. The + # effect of this is that the changes made to the MailList object will + # be aborted, which seems like the only sensible semantics. + # + # BAW: This may not be portable to other web servers or cgi execution + # models. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + if cgidata.has_key('bounce_matching_headers'): pairs = mlist.parse_matching_header_opt() @@ -135,8 +170,12 @@ FormatConfiguration(doc, mlist, 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 unconditional, so it's not an error if we unlock while + # we're already unlocked. mlist.Unlock() Index: Mailman/Cgi/admindb.py =================================================================== RCS file: /cvsroot/mailman/mailman/Mailman/Cgi/admindb.py,v retrieving revision 1.36.2.1 retrieving revision 1.36.2.4 diff -u -r1.36.2.1 -r1.36.2.4 --- Mailman/Cgi/admindb.py 2001/03/03 06:02:01 1.36.2.1 +++ Mailman/Cgi/admindb.py 2001/05/04 15:54:23 1.36.2.4 @@ -16,10 +16,12 @@ """Produce and process the pending-approval items for a list.""" +import sys import os import string import types import cgi +import signal from errno import ENOENT from Mailman import mm_cfg @@ -62,7 +64,7 @@ return # now that we have the list name, create the list object try: - mlist = MailList.MailList(listname) + mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: handle_no_list(doc, 'No such list %s
' % listname) syslog('error', 'No such list "%s": %s\n' % (listname, e)) @@ -71,14 +73,34 @@ # now we must authorize the user to view this page, and if they are, to # handle both the printing of the current outstanding requests, and the # selected actions + cgidata = cgi.FieldStorage() try: - cgidata = cgi.FieldStorage() - try: - Auth.authenticate(mlist, cgidata) - except Auth.NotLoggedInError, e: - Auth.loginpage(mlist, 'admindb', e.message) - return + Auth.authenticate(mlist, cgidata) + except Auth.NotLoggedInError, e: + Auth.loginpage(mlist, 'admindb', e.message) + return + + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + # + # BAW: Strictly speaking, the list 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. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + mlist.Lock() + try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + # If this is a form submission, then we'll process the requests and # print the results. otherwise (there are no keys in the form), we'll # print out the list of pending requests @@ -91,8 +113,8 @@ PrintRequests(mlist, doc) text = doc.Format(bgcolor="#ffffff") print text - finally: mlist.Save() + finally: mlist.Unlock() Index: Mailman/Cgi/handle_opts.py =================================================================== RCS file: /cvsroot/mailman/mailman/Mailman/Cgi/handle_opts.py,v retrieving revision 1.30 retrieving revision 1.30.2.2 diff -u -r1.30 -r1.30.2.2 --- Mailman/Cgi/handle_opts.py 2000/11/09 16:19:03 1.30 +++ Mailman/Cgi/handle_opts.py 2001/05/03 21:05:06 1.30.2.2 @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc. +# Copyright (C) 1998,1999,2000,2001 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 @@ -20,6 +20,7 @@ import os import string import cgi +import signal from Mailman import mm_cfg from Mailman import Utils @@ -61,7 +62,7 @@ user = parts[1] try: - mlist = MailList.MailList(listname) + mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: doc.AddItem(Header(2, "Error")) doc.AddItem(Bold('No such list %s' % listname)) @@ -69,10 +70,30 @@ syslog('error', 'No such list "%s": %s\n' % (listname, e)) return + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + # + # BAW: Strictly speaking, the list 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. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + process_form(mlist, user, doc) - finally: mlist.Save() + finally: mlist.Unlock() Index: Mailman/Cgi/subscribe.py =================================================================== RCS file: /cvsroot/mailman/mailman/Mailman/Cgi/subscribe.py,v retrieving revision 1.29 retrieving revision 1.29.2.1 diff -u -r1.29 -r1.29.2.1 --- Mailman/Cgi/subscribe.py 2000/09/29 00:05:05 1.29 +++ Mailman/Cgi/subscribe.py 2001/05/03 21:05:43 1.29.2.1 @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc. +# Copyright (C) 1998,1999,2000,2001 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 @@ -20,6 +20,7 @@ import os import string import cgi +import signal from Mailman import Utils from Mailman import MailList @@ -41,18 +42,38 @@ listname = string.lower(parts[0]) try: - mlist = MailList.MailList(listname) - mlist.IsListInitialized() + mlist = MailList.MailList(listname, lock=0) except Errors.MMListError, e: doc.AddItem(Header(2, "Error")) doc.AddItem(Bold('No such list %s' % listname)) print doc.Format(bgcolor="#ffffff") syslog('error', 'No such list "%s": %s\n' % (listname, e)) return + + # We need a signal handler to catch the SIGTERM that can come from Apache + # when the user hits the browser's STOP button. See the comment in + # admin.py for details. + # + # BAW: Strictly speaking, the list 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. + def sigterm_handler(signum, frame, mlist=mlist): + # Make sure the list gets unlocked... + mlist.Unlock() + # ...and ensure we exit, otherwise race conditions could cause us to + # enter MailList.Save() while we're in the unlocked state, and that + # could be bad! + sys.exit(0) + + mlist.Lock() try: + # Install the emergency shutdown signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + process_form(mlist, doc) - finally: mlist.Save() + finally: mlist.Unlock() Index: admin/www/download.ht =================================================================== RCS file: /cvsroot/mailman/mailman/admin/www/download.ht,v retrieving revision 1.5.2.5 retrieving revision 1.5.2.6 diff -u -r1.5.2.5 -r1.5.2.6 --- admin/www/download.ht 2001/04/18 10:44:14 1.5.2.5 +++ admin/www/download.ht 2001/05/03 21:09:36 1.5.2.6 @@ -65,9 +65,9 @@
Version -(2.0.4, +(2.0.5, released on -Apr 18 2001) +May 4 2001) is the current GNU release. It is available from the following mirror sites: