aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/Pending.py
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/Pending.py')
-rw-r--r--Mailman/Pending.py138
1 files changed, 94 insertions, 44 deletions
diff --git a/Mailman/Pending.py b/Mailman/Pending.py
index be1c6cac..0d6986cf 100644
--- a/Mailman/Pending.py
+++ b/Mailman/Pending.py
@@ -1,17 +1,17 @@
-# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2003 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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
-#
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
+# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
""" Track pending confirmation of subscriptions.
@@ -56,29 +56,48 @@ def new(*content):
# It's a programming error if this assertion fails! We do it this way so
# the assert test won't fail if the sequence is empty.
assert content[:1] in _ALLKEYS
- # Acquire the pending database lock, letting TimeOutError percolate up.
- lock = LockFile.LockFile(LOCKFILE)
- lock.lock(timeout=30)
+
+ # Get a lock handle now, but only lock inside the loop.
+ lock = LockFile.LockFile(LOCKFILE,
+ withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
+ # We try the main loop several times. If we get a lock error somewhere
+ # (for instance because someone broke the lock) we simply try again.
+ retries = mm_cfg.PENDINGDB_LOCK_ATTEMPTS
try:
- # Load the current database
- db = _load()
- # Calculate a unique cookie
- while 1:
- n = random.random()
- now = time.time()
- hashfood = str(now) + str(n) + str(content)
- cookie = sha.new(hashfood).hexdigest()
- if not db.has_key(cookie):
- break
- # Store the content, plus the time in the future when this entry will
- # be evicted from the database, due to staleness.
- db[cookie] = content
- evictions = db.setdefault('evictions', {})
- evictions[cookie] = now + mm_cfg.PENDING_REQUEST_LIFE
- _save(db)
- return cookie
+ while retries:
+ retries -= 1
+ if not lock.locked():
+ try:
+ lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
+ except LockFile.TimeOutError:
+ continue
+ # Load the current database
+ db = _load()
+ # Calculate a unique cookie
+ while 1:
+ n = random.random()
+ now = time.time()
+ hashfood = str(now) + str(n) + str(content)
+ cookie = sha.new(hashfood).hexdigest()
+ if not db.has_key(cookie):
+ break
+ # Store the content, plus the time in the future when this entry
+ # will be evicted from the database, due to staleness.
+ db[cookie] = content
+ evictions = db.setdefault('evictions', {})
+ evictions[cookie] = now + mm_cfg.PENDING_REQUEST_LIFE
+ try:
+ _save(db, lock)
+ except LockFile.NotLockedError:
+ continue
+ return cookie
+ else:
+ # We failed to get the lock or keep it long enough to save the
+ # data!
+ raise LockFile.TimeOutError
finally:
- lock.unlock()
+ if lock.locked():
+ lock.unlock()
@@ -88,30 +107,53 @@ def confirm(cookie, expunge=1):
If optional expunge is true (the default), the record is also removed from
the database.
"""
- # Acquire the pending database lock, letting TimeOutError percolate up.
- # BAW: we perhaps shouldn't acquire the lock if expunge==0.
- lock = LockFile.LockFile(LOCKFILE)
- lock.lock(timeout=30)
- try:
- # Load the database
+ if not expunge:
db = _load()
missing = []
content = db.get(cookie, missing)
if content is missing:
return None
- # Remove the entry from the database
- if expunge:
+ return content
+
+ # Get a lock handle now, but only lock inside the loop.
+ lock = LockFile.LockFile(LOCKFILE,
+ withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
+ # We try the main loop several times. If we get a lock error somewhere
+ # (for instance because someone broke the lock) we simply try again.
+ retries = mm_cfg.PENDINGDB_LOCK_ATTEMPTS
+ try:
+ while retries:
+ retries -= 1
+ if not lock.locked():
+ try:
+ lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
+ except LockFile.TimeOutError:
+ continue
+ # Load the database
+ db = _load()
+ missing = []
+ content = db.get(cookie, missing)
+ if content is missing:
+ return None
del db[cookie]
del db['evictions'][cookie]
- _save(db)
- return content
+ try:
+ _save(db, lock)
+ except LockFile.NotLockedError:
+ continue
+ return content
+ else:
+ # We failed to get the lock and keep it long enough to save the
+ # data!
+ raise LockFile.TimeOutError
finally:
- lock.unlock()
+ if lock.locked():
+ lock.unlock()
def _load():
- # The list's lock must be acquired.
+ # The list's lock must be acquired if you wish to alter data and save.
#
# First try to load the pickle file
fp = None
@@ -134,8 +176,10 @@ def _load():
fp.close()
-def _save(db):
- # Lock must be acquired.
+def _save(db, lock):
+ # Lock must be acquired before loading the data that is now being saved.
+ if not lock.locked():
+ raise LockFile.NotLockedError
evictions = db['evictions']
now = time.time()
for cookie, data in db.items():
@@ -154,13 +198,17 @@ def _save(db):
omask = os.umask(007)
# Always save this as a pickle (safely), and after that succeeds, blow
# away any old marshal file.
- tmpfile = PCKFILE + '.tmp'
+ tmpfile = '%s.tmp.%d.%d' % (PCKFILE, os.getpid(), now)
fp = None
try:
fp = open(tmpfile, 'w')
cPickle.dump(db, fp)
fp.close()
fp = None
+ if not lock.locked():
+ # Our lock was broken?
+ os.remove(tmpfile)
+ raise LockFile.NotLockedError
os.rename(tmpfile, PCKFILE)
if os.path.exists(DBFILE):
os.remove(DBFILE)
@@ -173,8 +221,9 @@ def _save(db):
def _update(olddb):
# Update an old pending_subscriptions.db database to the new format
- lock = LockFile.LockFile(LOCKFILE)
- lock.lock(timeout=30)
+ lock = LockFile.LockFile(LOCKFILE,
+ withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
+ lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
try:
# We don't need this entry anymore
if olddb.has_key('lastculltime'):
@@ -199,6 +248,7 @@ def _update(olddb):
# request was made. The new format keeps it as the time the
# request should be evicted.
evictions[cookie] = data[-1] + mm_cfg.PENDING_REQUEST_LIFE
- _save(db)
+ _save(db, lock)
finally:
- lock.unlock()
+ if lock.locked():
+ lock.unlock()