#
# This module was written in 2007 by S James S Stapleton
# This module is released as PUBLIC DOMAIN
#
# Please respect the creator/author's intent and release all
# modifications as public domain. Thank you.
#

from time import time
from time import sleep
import weakref

import inheritable

try: import threading as threading
except Exception, e:
    try: import dummy_threading as threading
    except Exception, f:
        raise Excepion(str(e) + "\n\n" + str(f))

try: import thread as thread
except Exception, e:
    try: import dummy_thread as thread
    except Exception, f:
        raise Excepion(str(e) + "\n\n" + str(f))


import lockholder

class RWrlock(inheritable.Irlock):
    """
This is a read/write mode lock. The concept is that each time a lock is requested, it is either a _read_ or _write_ lock. There can
be infinite concurrent read locks, but only one write lock. The write lock may not exist while there are any read locks. Several
optimisation assumptions are made.
1) The total amount of time increased by adding an additional concurrent thread is less than running one thread only after another
   is completed
2) Only writes need exclusive locks
3) It's better to improve the average completion time of all _n_ threads than to have the _n_ threads execute in order.
4) Requested by one the same thread cannot interfere with eachother.
5) No thread will request read locks after write locks, unless the write locks are released first
6) ... and vice versax

So the process is:
Read threads are run if any of the following are true:
(a) No thread hold the lock
(b) Only read threads hold the lock, and there are no scheduled write threads
(c) A write thread is the last thread to release the lock and read threads are queued (all such read threads get a lock).

Write threads are run if any of the following are true:
(a) No threads are holding the lock, or the lock is held by the active write thread
(b) It is the next queued write thread, and there are no queued read threads
(c) It is the next queued write thread, there are no active threads, and the last unlocked thread was a read thread.
    """
    READ             = 0
    WRITE            = 1

    __read_locked    = None #Which read threads have locks, and their counts [threadid]->count dict
    __read_pending   = None #How many pending read threads? count
    #no read queue - they'll periodically poll.
    __write_locked   = None #Which thread is locked? threadid
    __write_queued   = None #Which threads are waiting to read? array of threadid
    __write_count    = None #Will be an integer, semaphores are unnecessary as one thread will not
                            #conflict with itself
    __arevent        = None #set to true when reads are allowed
    __awevent        = None #set to true when writes are allowed
    __meevent        = None #set to true when the lock for data on this can be attained.

    def _status(self):
        """
print debugging stuff to console
        """
        print "Lock status:"
        print "       EVENTS: "
        print "        arevent: " + str(self.__arevent.isSet())
        print "        awevent: " + str(self.__awevent.isSet())
        print "        meevent: " + str(self.__meevent.isSet())
        print "  LOCK STATUS:"
        print "    read locked: "
        for k in self.__read_locked:
            print "               : " + str(k) + " --> " + str(self.__read_locked[k])
        print "   read pending: " + str(self.__read_pending)
        print "   write locked: " + str(self.__write_locked)
        print "    write count: " + str(self.__write_count)
        if(len(self.__write_queued) == 0):
            print "   write queued: None"
        else:
            print "   write queued: "
            for t in self.__write_queued:
                print "               : " + str(t)
    #def _status(self):

    def __init__(self):
        """
Initialization:
holder_type: The Slock[G|E] to act as a lock holder for saquire. lockholder.SlockE is the default. It will sleep between lock
             attempts. the G variant does not auto-sleep itself. Either G or E class can fill this argument, or a defined
             subclass (useful for changing the defaults.
        """
        inheritable.Irlock.__init__(self)
        self.__read_locked  = {}
        self.__read_pending = 0
        self.__write_locked = None
        self.__write_count  = 0
        self.__write_queued = []
        #allow reading and writing events:
        self.__arevent      = threading.Event()
        self.__awevent      = threading.Event()
        self.__meevent      = threading.Event()
        self.__arevent.set()
        self.__awevent.set()
        self.__meevent.set()
    #def __init__(self):

    def __acquire(self, blocking=1, timeout=None):
        """
An internal lock to call the standard lock function
        """
        if(timeout == None):
            res = inheritable.Irlock.acquire(self, blocking)
            if(res):
                self.__meevent.clear() #should be unset if I can aquire a lock
            return res
        else:
            start = time()
            while(time()-start < timeout):
                self.__meevent.wait(timeout-(time()-start))
                if(inheritable.Irlock.acquire(self, 0)):
                    self.__meevent.clear()
                    return True
            #while(time()-start < timeout):
            return False
    #def __acquire(self, blocking):

    def __release(self):
        inheritable.Irlock.release(self)
        #this wont be cleared until a lock is allocated, and is only used to notify others the lock is free,
        #so we only set /after/ the lock is released, to prevent short timeouts from timing out when they
        #shouldn't (activated before the lock comes alive)
        self.__meevent.set()
    #def __release(self):

    def __set_read_lock(self):
        """
A lock on this object must be obtained prior to, and held during the call to this function.

This manages the internals of acquiring a read lock for the current thread.
        """
        #add this to the locked read thread list, and
        #ensure write threads are disallowed.
        this = thread.get_ident()
        if(self.__awevent.isSet()):
            self.__awevent.clear()
        if(self.__read_locked.has_key(this)):
            self.__read_locked[this] += 1
        else:
            self.__read_locked[this] = 1
    #def __set_read_lock(self):

    def __release_read_locked(self):
        #remove this from the read thread list. If write threads are disallowed, allow them.
        this = thread.get_ident()
        self.__read_locked[this] -= 1
        if(self.__read_locked[this] == 0):
            del self.__read_locked[this]
        if(len(self.__read_locked) == 0):
            #now, we will release write locks if there are
            #queued writes, read locks and write locks if not:
            if(len(self.__write_queued) > 0):
                self.__awevent.set()
            else:
                self.__awevent.set()
                self.__arevent.set()
    #def __release_read_locked(self):

    def __set_write_lock(self):
        #set this as the active write thread,
        #and disallow any read/write threads.
        if(self.__write_locked == None): #it's not already write locked, grab it
            this = thread.get_ident()
            self.__write_locked = this
        #update the number of write locks
        self.__write_count += 1
        self.__awevent.clear()
        #turn off the allow-read-event if it is set.
        if(self.__arevent.isSet()):
            self.__arevent.clear()
    #def __set_write_lock(self):

    def __release_write_locked(self):
        if(self.__write_count > 0): #we've got locks, remove one
            self.__write_count -= 1
            if(self.__write_count == 0): #last lock removed, release completely
                self.__write_locked = None
                #If there are read threads pending, release them only,
                #otherwise, release read and write threads both:
                if(self.__read_pending > 0):
                    self.__arevent.set()
                else:
                    self.__awevent.set()
                    self.__arevent.set()
                #if(self.__read_pending > 0):
            #if(self.__write_count == 0): #last lock removed, release completely
        #if(self.__write_count > 0): #we've got locks, remove one
    #def __release_write_locked(self):

    def acquire(self, typ, blocking=1):
        """
Attempt to acquire a lock.
typ      - Lock type, RWlock.READ is a reading lock, and RWlock.WRITE is a writing lock. reading locks can be concurrent,
           but writing cannot.
blocking - Des this block or not, default is to block. Us sacquire/eacquire and gacquire to allow for timeouts.

returns true with a lock granted, false without, None on error
        """
        this = thread.get_ident()
        if(typ == self.READ):
            #acquire a lock on itself to check the locking variables
            if(not blocking):
                if(not self.__acquire(0)):
                    return False
                if(not self.__arevent.isSet() or len(self.__write_queued) > 0):
                    #read thread acquisition prohibited
                    self.__release()
                    return False
                result = True
                #get the read lock.
                self.__set_read_lock()
                self.__release()
                return result
            else: #blocking
                self.__acquire(1)
                if(self.__arevent.isSet() and len(self.__write_queued) == 0):
                   #reading allowed explicitly, and not blocked by pending writes
                    self.__set_read_lock()
                    self.__release()
                    return True
                #reading is not allowed, release the lock, and wait on another read chance
                writewait = False #set to true if we need to wait on a write:
                self.__read_pending += 1
                if(len(self.__write_queued) > 0 and not self.__write_locked):
                    #we check here, and execute after relese to prevent a race
                    writewait = True
                self.__release()
                if(writewait):
                    self.__awevent.wait()
                while(True):
                    #we get our write event, lock state, ensure we still have allowed writes, acquire lock, release state
                    #inside of a while(true) since arevent may change between wakeup and check
                    self.__arevent.wait()
                    self.__acquire(1)
                    if(self.__arevent.isSet()):
                        self.__set_read_lock()
                        self.__read_pending -= 1
                        self.__release()
                        return True
                    self.__release()
                #while(True):
            #if(not blocking):/else
        elif(typ == self.WRITE):
            if(not blocking):
                if(not self.__acquire(0)):
                    return False
                #always succeed if the lock is owned by the requesting thread:
                if(self.__write_locked == this):
                    self.__set_write_lock()
                    self.__release()
                    return True
                #fail if writes are pohibited or there are queued reads/writes
                if(not self.__awevent.isSet() or
                   len(self.__write_queued) > 0 or self.__read_pending > 0):
                    #we don't can't get in if /anything/ is ahead of us.
                    self.__release()
                    return False
                result = True
                self.__set_write_lock()
                self.__release()
                return result
            else:
                self.__acquire(1)
                #auto-aquire if this thread already has the lock:
                if(self.__write_locked == this):
                    self.__set_write_lock()
                    self.__release()
                    return True
                #gain a lock if writes are allowed and none are queued:
                if(self.__awevent.isSet()):
                    if(len(self.__write_queued) == 0):
                        #no locks or queues, we go.
                        self.__set_write_lock()
                        self.__release()
                        return True
                #there is something locked or ahead of us, wait.
                self.__write_queued.append(this)
                self.__release()
                while(True):
                    #wait until writes are allowed, attain a situation lock, check to see if writes are still allowed
                    #and we are first in queue, if so, lock and release situation lock, otherwise release situation lock
                    self.__awevent.wait()
                    self.__acquire(1)
                    #auto acquire if this thread alredy has the lock
                    if(self.__write_locked == this):
                        self.__set_write_lock()
                        self.__release()
                        return True
                    if(self.__awevent.isSet()):
                        if(self.__write_queued[0] == this):
                            #nothing ahead of us, nothing locked:
                            self.__write_queued.pop(0)
                            self.__set_write_lock()
                            self.__release()
                            return True
                    if(self.__write_locked == this): #wont have __awevent set, and shouldn't so don't rely on that.
                        i = 0
                        #get the next entry this thread has in the queue, we don't check for overflow because nothing else but
                        #this library should be accessing the queue, and no thread can take itself out more times than it put
                        #itself in, and it can get here without having a copy (not taken out) in.
                        while(self.__write_queued[i] != this):
                            i+=1
                        self.__write_queued.pop(i)
                        self.__set_write_lock()
                        self.__release()
                    self.__release()
                    sleep(0) #suggest others be allowed to play to reduce wasted cycles, since the queue won't change
                #while(True):
            #if(not blocking):/else
        else:
            return None
        #elif(typ == self.[READ|WRITE]):
    #def acquire(self, blocking):
        
    def tacquire(self, timeout=True):
        this = thread.get_ident()
        if(type(timeout) == int):
            timeout = float(timeout)
        if(type(timeout) == float):
            start = time() #timeouts for waits are time()-start

            if(typ == self.READ):
                self.__acquire(timeout=timeout-(time()-start))
                if(self.__arevent.isSet() and len(self.__write_queued) == 0):
                   #reading allowed explicitly, and not blocked by pending writes
                    self.__set_read_lock()
                    self.__release()
                    return True
                #reading is not allowed, release the lock, and wait on another read chance
                writewait = False #set to true if we need to wait on a write:
                self.__read_pending += 1
                if(len(self.__write_queued) > 0 and not self.__write_locked):
                    #we check here, and execute after relese to prevent a race
                    writewait = True
                self.__release()
                self.__awevent.wait(timeout-(time()-start))
                
                while(time()-start < timeout):
                    #we get our write event, lock state, ensure we still have allowed writes, acquire lock, release state
                    #inside of a while(true) since arevent may change between wakeup and check
                    self.__arevent.wait(timeout-(time()-start))
                    self.__acquire(timeout-(time()-start))
                    if(self.__arevent.isSet()):
                        self.__set_read_lock()
                        self.__read_pending -= 1
                        self.__release()
                        return True
                    self.__release()
                #while(time()-start < timeout):
                return False

            elif(typ == self.WRITE):
                self.__acquire(timeout=timeout-(time()-start))
                #auto acquire if this thread alredy has the lock
                if(self.__write_locked == this):
                    self.__set_write_lock()
                    self.__release()
                    return True
                #gain a lock if writes are allowed and none are queued:
                if(self.__awevent.isSet() and len(self.__write_queued) == 0):
                    #no locks or queues, we go.
                    self.__set_write_lock()
                    self.__release()
                    return True
                #there is something locked or ahead of us, wait.
                self.__write_queued.append(this)
                self.__release()
                while(time()-start < timeout):
                    #wait until writes are allowed, attain a situation lock, check to see if writes are still allowed
                    #and we are first in queue, if so, lock and release situation lock, otherwise release situation lock
                    self.__awevent.wait(timeout-(time()-start))
                    self.__acquire(timeout=timeout-(time()-start))
                    if(self.__awevent.isSet()):
                        if(self.__write_queued[0] == this):
                            #nothing ahead of us, nothing locked:
                            self.__write_queued.pop(0)
                            self.__set_write_lock()
                            self.__release()
                            return True
                    if(self.__write_locked == this): #wont have __awevent set, and shouldn't so don't rely on that.
                        i = 0
                        #get the next entry this thread has in the queue, we don't check for overflow because nothing else but
                        #this library should be accessing the queue, and no thread can take itself out more times than it put
                        #itself in, and it can get here without having a copy (not taken out) in.
                        while(self.__write_queued[i] != this):
                            i+=1
                        self.__write_queued.pop(i)
                        self.__set_write_lock()
                        self.__release()
                        return True
                    self.__release()
                    sleep(0) #suggest others be allowed to play to reduce wasted cycles, since the queue won't change
                #while(time()-start < timeout):
            else:
                return None
            #[el]if(typ == self.[READ|WRITE]):\else:
        #if(type(timeout) == int)
        return self.acquire(typ, timeout)
    #def eacquire(self, timeout=True):

    def release(self):
        this = thread.get_ident()
        self.__acquire()
        if(self.__write_locked != None and len(self.__read_locked) > 0):
            self.__release()
            raise Exception("Error, cannot release a thread, both a write and read lock are present!")
        #release the write lock, if there is one
        if(self.__write_locked and self.__write_locked == this):
            self.__release_write_locked()
        if(len(self.__read_locked) > 0 and self.__read_locked.has_key(this)):
            self.__release_read_locked()
        self.__release()
    #def release(self):
#class Slock(thread.LockType):

