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>