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>