diff options
Diffstat (limited to 'Mailman/Pending.py')
-rw-r--r-- | Mailman/Pending.py | 138 |
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() |