mirror of
https://github.com/retspen/webvirtcloud
synced 2024-11-01 12:04:15 +00:00
227 lines
10 KiB
Python
227 lines
10 KiB
Python
|
# -*- coding: iso-8859-15 -*-
|
||
|
"""locks.py - Read-Write lock thread lock implementation
|
||
|
|
||
|
See the class documentation for more info.
|
||
|
|
||
|
Copyright (C) 2007, Heiko Wundram.
|
||
|
Released under the BSD-license.
|
||
|
|
||
|
found at: http://code.activestate.com/recipes/502283-read-write-lock-class-rlock-like/
|
||
|
"""
|
||
|
|
||
|
# Imports
|
||
|
# -------
|
||
|
|
||
|
from threading import Condition, Lock, currentThread
|
||
|
from time import time
|
||
|
|
||
|
|
||
|
# Read write lock
|
||
|
# ---------------
|
||
|
|
||
|
class ReadWriteLock(object):
|
||
|
"""Read-Write lock class. A read-write lock differs from a standard
|
||
|
threading.RLock() by allowing multiple threads to simultaneously hold a
|
||
|
read lock, while allowing only a single thread to hold a write lock at the
|
||
|
same point of time.
|
||
|
|
||
|
When a read lock is requested while a write lock is held, the reader
|
||
|
is blocked; when a write lock is requested while another write lock is
|
||
|
held or there are read locks, the writer is blocked.
|
||
|
|
||
|
Writers are always preferred by this implementation: if there are blocked
|
||
|
threads waiting for a write lock, current readers may request more read
|
||
|
locks (which they eventually should free, as they starve the waiting
|
||
|
writers otherwise), but a new thread requesting a read lock will not
|
||
|
be granted one, and block. This might mean starvation for readers if
|
||
|
two writer threads interweave their calls to acquireWrite() without
|
||
|
leaving a window only for readers.
|
||
|
|
||
|
In case a current reader requests a write lock, this can and will be
|
||
|
satisfied without giving up the read locks first, but, only one thread
|
||
|
may perform this kind of lock upgrade, as a deadlock would otherwise
|
||
|
occur. After the write lock has been granted, the thread will hold a
|
||
|
full write lock, and not be downgraded after the upgrading call to
|
||
|
acquireWrite() has been match by a corresponding release().
|
||
|
"""
|
||
|
|
||
|
def __init__(self):
|
||
|
"""Initialize this read-write lock."""
|
||
|
|
||
|
# Condition variable, used to signal waiters of a change in object
|
||
|
# state.
|
||
|
self.__condition = Condition(Lock())
|
||
|
|
||
|
# Initialize with no writers.
|
||
|
self.__writer = None
|
||
|
self.__upgradewritercount = 0
|
||
|
self.__pendingwriters = []
|
||
|
|
||
|
# Initialize with no readers.
|
||
|
self.__readers = {}
|
||
|
|
||
|
def acquireRead(self, timeout=None):
|
||
|
"""Acquire a read lock for the current thread, waiting at most
|
||
|
timeout seconds or doing a non-blocking check in case timeout is <= 0.
|
||
|
|
||
|
In case timeout is None, the call to acquireRead blocks until the
|
||
|
lock request can be serviced.
|
||
|
|
||
|
In case the timeout expires before the lock could be serviced, a
|
||
|
RuntimeError is thrown."""
|
||
|
|
||
|
if timeout is not None:
|
||
|
endtime = time() + timeout
|
||
|
me = currentThread()
|
||
|
self.__condition.acquire()
|
||
|
try:
|
||
|
if self.__writer is me:
|
||
|
# If we are the writer, grant a new read lock, always.
|
||
|
self.__writercount += 1
|
||
|
return
|
||
|
while True:
|
||
|
if self.__writer is None:
|
||
|
# Only test anything if there is no current writer.
|
||
|
if self.__upgradewritercount or self.__pendingwriters:
|
||
|
if me in self.__readers:
|
||
|
# Only grant a read lock if we already have one
|
||
|
# in case writers are waiting for their turn.
|
||
|
# This means that writers can't easily get starved
|
||
|
# (but see below, readers can).
|
||
|
self.__readers[me] += 1
|
||
|
return
|
||
|
# No, we aren't a reader (yet), wait for our turn.
|
||
|
else:
|
||
|
# Grant a new read lock, always, in case there are
|
||
|
# no pending writers (and no writer).
|
||
|
self.__readers[me] = self.__readers.get(me, 0) + 1
|
||
|
return
|
||
|
if timeout is not None:
|
||
|
remaining = endtime - time()
|
||
|
if remaining <= 0:
|
||
|
# Timeout has expired, signal caller of this.
|
||
|
raise RuntimeError("Acquiring read lock timed out")
|
||
|
self.__condition.wait(remaining)
|
||
|
else:
|
||
|
self.__condition.wait()
|
||
|
finally:
|
||
|
self.__condition.release()
|
||
|
|
||
|
def acquireWrite(self, timeout=None):
|
||
|
"""Acquire a write lock for the current thread, waiting at most
|
||
|
timeout seconds or doing a non-blocking check in case timeout is <= 0.
|
||
|
|
||
|
In case the write lock cannot be serviced due to the deadlock
|
||
|
condition mentioned above, a ValueError is raised.
|
||
|
|
||
|
In case timeout is None, the call to acquireWrite blocks until the
|
||
|
lock request can be serviced.
|
||
|
|
||
|
In case the timeout expires before the lock could be serviced, a
|
||
|
RuntimeError is thrown."""
|
||
|
|
||
|
if timeout is not None:
|
||
|
endtime = time() + timeout
|
||
|
me, upgradewriter = currentThread(), False
|
||
|
self.__condition.acquire()
|
||
|
try:
|
||
|
if self.__writer is me:
|
||
|
# If we are the writer, grant a new write lock, always.
|
||
|
self.__writercount += 1
|
||
|
return
|
||
|
elif me in self.__readers:
|
||
|
# If we are a reader, no need to add us to pendingwriters,
|
||
|
# we get the upgradewriter slot.
|
||
|
if self.__upgradewritercount:
|
||
|
# If we are a reader and want to upgrade, and someone
|
||
|
# else also wants to upgrade, there is no way we can do
|
||
|
# this except if one of us releases all his read locks.
|
||
|
# Signal this to user.
|
||
|
raise ValueError(
|
||
|
"Inevitable dead lock, denying write lock"
|
||
|
)
|
||
|
upgradewriter = True
|
||
|
self.__upgradewritercount = self.__readers.pop(me)
|
||
|
else:
|
||
|
# We aren't a reader, so add us to the pending writers queue
|
||
|
# for synchronization with the readers.
|
||
|
self.__pendingwriters.append(me)
|
||
|
while True:
|
||
|
if not self.__readers and self.__writer is None:
|
||
|
# Only test anything if there are no readers and writers.
|
||
|
if self.__upgradewritercount:
|
||
|
if upgradewriter:
|
||
|
# There is a writer to upgrade, and it's us. Take
|
||
|
# the write lock.
|
||
|
self.__writer = me
|
||
|
self.__writercount = self.__upgradewritercount + 1
|
||
|
self.__upgradewritercount = 0
|
||
|
return
|
||
|
# There is a writer to upgrade, but it's not us.
|
||
|
# Always leave the upgrade writer the advance slot,
|
||
|
# because he presumes he'll get a write lock directly
|
||
|
# from a previously held read lock.
|
||
|
elif self.__pendingwriters[0] is me:
|
||
|
# If there are no readers and writers, it's always
|
||
|
# fine for us to take the writer slot, removing us
|
||
|
# from the pending writers queue.
|
||
|
# This might mean starvation for readers, though.
|
||
|
self.__writer = me
|
||
|
self.__writercount = 1
|
||
|
self.__pendingwriters = self.__pendingwriters[1:]
|
||
|
return
|
||
|
if timeout is not None:
|
||
|
remaining = endtime - time()
|
||
|
if remaining <= 0:
|
||
|
# Timeout has expired, signal caller of this.
|
||
|
if upgradewriter:
|
||
|
# Put us back on the reader queue. No need to
|
||
|
# signal anyone of this change, because no other
|
||
|
# writer could've taken our spot before we got
|
||
|
# here (because of remaining readers), as the test
|
||
|
# for proper conditions is at the start of the
|
||
|
# loop, not at the end.
|
||
|
self.__readers[me] = self.__upgradewritercount
|
||
|
self.__upgradewritercount = 0
|
||
|
else:
|
||
|
# We were a simple pending writer, just remove us
|
||
|
# from the FIFO list.
|
||
|
self.__pendingwriters.remove(me)
|
||
|
raise RuntimeError("Acquiring write lock timed out")
|
||
|
self.__condition.wait(remaining)
|
||
|
else:
|
||
|
self.__condition.wait()
|
||
|
finally:
|
||
|
self.__condition.release()
|
||
|
|
||
|
def release(self):
|
||
|
"""Release the currently held lock.
|
||
|
|
||
|
In case the current thread holds no lock, a ValueError is thrown."""
|
||
|
|
||
|
me = currentThread()
|
||
|
self.__condition.acquire()
|
||
|
try:
|
||
|
if self.__writer is me:
|
||
|
# We are the writer, take one nesting depth away.
|
||
|
self.__writercount -= 1
|
||
|
if not self.__writercount:
|
||
|
# No more write locks; take our writer position away and
|
||
|
# notify waiters of the new circumstances.
|
||
|
self.__writer = None
|
||
|
self.__condition.notifyAll()
|
||
|
elif me in self.__readers:
|
||
|
# We are a reader currently, take one nesting depth away.
|
||
|
self.__readers[me] -= 1
|
||
|
if not self.__readers[me]:
|
||
|
# No more read locks, take our reader position away.
|
||
|
del self.__readers[me]
|
||
|
if not self.__readers:
|
||
|
# No more readers, notify waiters of the new
|
||
|
# circumstances.
|
||
|
self.__condition.notifyAll()
|
||
|
else:
|
||
|
raise ValueError("Trying to release unheld lock")
|
||
|
finally:
|
||
|
self.__condition.release()
|