[Mailman-Announce] ANNOUNCE Mailman 2.0.5

Barry A. Warsaw mailman-developers@python.org
Mon, 7 May 2001 12:24:16 -0400


Folks,

I've just released Mailman 2.0.5 which fixes a problem with stale lock
files that can occur under certain situations when the user hits the
`Stop' button on their browser.  These stale lock files can cause
mailing lists to be inaccessible for long periods of time (until the
stale lock file times out or is manually removed).

As usual, I'm releasing this as both a complete tarball and as a patch
against Mailman 2.0.4.  You /must/ update your source to 2.0.4 before
applying the 2.0.5 patch.  Since the patch is small, I'm including it
in this message.  To apply, cd into your 2.0.5 source tree and apply
it like so:

    % patch -p0 < mailman-2.0.4-2.0.5.txt
    
Currently both http://mailman.sourceforge.net and http://www.list.org
are updated, and I expect the gnu.org site to be updated soon as
well.  The release information on SF is at

    http://sourceforge.net/project/shownotes.php?release_id=31693

See also

    http://www.gnu.org/software/mailman
    http://www.list.org
    http://mailman.sourceforge.net

My thanks to those of you who gave the 2.0.5 pre-release a try!

Enjoy,
-Barry

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 <em>%s</em>' % 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 <em>%s</em><p>' % 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 <em>%s</em>' % 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 <em>%s</em>' % 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 @@
 <h3>Downloading</h3>
 
 <p>Version
-(<!-VERSION--->2.0.4<!-VERSION--->,
+(<!-VERSION--->2.0.5<!-VERSION--->,
 released on
-<!-DATE--->Apr 18 2001<!-DATE--->)
+<!-DATE--->May  4 2001<!-DATE--->)
 is the current GNU release.  It is available from the following mirror sites:
 
 <ul>
Index: admin/www/download.html
===================================================================
RCS file: /cvsroot/mailman/mailman/admin/www/download.html,v
retrieving revision 1.6.2.7
retrieving revision 1.6.2.8
diff -u -r1.6.2.7 -r1.6.2.8
--- admin/www/download.html	2001/04/18 10:44:14	1.6.2.7
+++ admin/www/download.html	2001/05/03 21:09:36	1.6.2.8
@@ -1,6 +1,6 @@
 <HTML>
 <!-- THIS PAGE IS AUTOMATICALLY GENERATED.  DO NOT EDIT. -->
-<!-- Wed Apr 18 06:43:32 2001 -->
+<!-- Thu May  3 17:09:03 2001 -->
 <!-- USING HT2HTML 1.1 -->
 <!-- SEE http://www.wooz.org/barry/software/pyware.html -->
 <!-- User-specified headers:
@@ -237,9 +237,9 @@
 <h3>Downloading</h3>
 
 <p>Version
-(<!-VERSION--->2.0.4<!-VERSION--->,
+(<!-VERSION--->2.0.5<!-VERSION--->,
 released on
-<!-DATE--->Apr 18 2001<!-DATE--->)
+<!-DATE--->May  4 2001<!-DATE--->)
 is the current GNU release.  It is available from the following mirror sites:
 
 <ul>