Example #1
0
        /// <summary>
        /// Compare this event's priority to the other group's priority. Finds and returns the highest priority event.
        /// </summary>
        /// <param name="previousHighest"></param>
        /// <param name="otherGroup">MUST HAVE THE LOCK ON THIS GROUP'S MUTEX</param>
        /// <returns></returns>
        private LockableLock chooseHighestPriority(LockableLock previousHighest, LockableLockGroup otherGroup)
        {
            LockableLock otherHighest = otherGroup.highestPriority();
            int          priority     = previousHighest.comparePriority(otherHighest);

            if (priority == 0)
            {
                if (otherGroup.subSubPriority == -1)
                {
                    otherGroup.subSubPriority = Interlocked.Increment(ref LockableLockGroup.PriorityChooser);
                }
                if (previousHighest.group.subSubPriority == -1)
                {
                    //Previous event doesn't have a priority yet and will get a new value later, which must be higher than otherGroup's value.
                    //So the other event has priority.
                    priority = -1;
                }
                else
                {
                    priority = otherHighest.group.subSubPriority - previousHighest.group.subSubPriority;
                }
            }
            if (priority < 0)
            {
                return(otherHighest);
            }
            return(previousHighest);
        }
Example #2
0
 /// <summary>
 /// Checks of this lock contains the other lock. Should only be called by this own thread while it's active, assumes
 /// there's no race conditions.
 /// </summary>
 /// <param name="other"></param>
 /// <param name="recursive">True if subgroups of subgroups should be searched</param>
 /// <returns></returns>
 private bool contains(LockableLockGroup other, bool recursive)
 {
     if (other == this)
     {
         return(true);
     }
     foreach (LockableLock nextEvent in this.eventQueue)
     {
         if (nextEvent.ownedSubgroups == null)
         {
             continue;
         }
         foreach (LockableLockGroup nextGroup in nextEvent.ownedSubgroups)
         {
             if (recursive)
             {
                 if (nextGroup.contains(other, recursive))
                 {
                     return(true);
                 }
             }
             else if (nextGroup == other)
             {
                 return(true);
             }
         }
     }
     return(false);
 }
Example #3
0
 /// <summary>
 /// Make sure an event contains a subgroup. Does nothing if it already does.
 /// Handles interrupts for events in that subgroup, and also interrupts for other groups using that subgroup.
 /// </summary>
 /// <param name="currentEvent">Event to add the subgroup to. Must be part of this lock group.</param>
 /// <param name="newSubgroup">Subgroup to try to add to the event.</param>
 private void addToLockableLock(LockableLock currentEvent, LockableLockGroup newSubgroup)
 {
     lock (statusMutex)
     {
         if (currentEvent.ownedSubgroups == null)
         {
             currentEvent.ownedSubgroups = new List <LockableLockGroup>();
         }
         if (currentEvent.ownedSubgroups.Contains(newSubgroup))
         {
             return;
         }
         //Don't have the lock on other lock groups, but that's okay, because they already said they are waiting
         //and this will be the thread that wakes them up, so they won't modify themselves right now.
         currentEvent.ownedSubgroups.Add(newSubgroup);
     }
     foreach (LockableLock otherEvent in newSubgroup.eventQueue)
     {
         currentEvent.holder.InterruptOtherEvent(otherEvent.holder, false);
         otherEvent.holder.RespondToInterrupt(currentEvent.holder, false);
     }
     foreach (LockableLockGroup interruptedGroup in containedGroups(true))
     {
         interruptedGroup.interruptAllWithGroup(newSubgroup, currentEvent.holder);
     }
 }
Example #4
0
 /// <summary>
 /// </summary>
 /// <param name="newSubgroup">Subgroup being used by a new event</param>
 /// <param name="forEvent">Event interrupting things by taking the subgroup</param>
 private void interruptAllWithGroup(LockableLockGroup newSubgroup, ILockHolder forEvent)
 {
     foreach (LockableLock myEvent in eventQueue)
     {
         if (myEvent.ownedSubgroups != null && myEvent.ownedSubgroups.Contains(newSubgroup))
         {
             forEvent.InterruptOtherEvent(myEvent.holder, false);
             myEvent.holder.RespondToInterrupt(forEvent, false);
         }
     }
 }
Example #5
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="objectToLock"></param>
        /// <param name="forEvent"></param>
        /// <returns></returns>
        internal static LockableLock EnterLock(ILockable objectToLock, ILockHolder forEvent, int timeout = Timeout.Infinite)
        {
            LockableLockGroup currentLock;
            DateTime          startTime = DateTime.UtcNow;

            //Check if this is a subsequent event in a single thread
            if (LockableLockGroup.CurrentLockGroup != null)
            {
                currentLock = LockableLockGroup.CurrentLockGroup;
                foreach (LockableLock ancestorEvent in currentLock.eventQueue)
                {
                    forEvent.InterruptOtherEvent(ancestorEvent.holder, true);
                    ancestorEvent.holder.RespondToInterrupt(forEvent, true);
                }
                LockableLock newLock;
                lock (currentLock.statusMutex)
                {
                    newLock = currentLock.addEvent(forEvent);
                }
                currentLock.AddResource(objectToLock, newLock);
                return(newLock);
            }

            while (true)
            {
                if (Monitor.TryEnter(objectToLock.LockMutex, startTime.RemainingTimeout(timeout)))
                {
                    try
                    {
                        currentLock = objectToLock.CurrentLock;
                        if (currentLock == null || currentLock.eventQueue.Count == 0)
                        {
                            currentLock = new LockableLockGroup();

                            objectToLock.CurrentLock           = currentLock;
                            LockableLockGroup.CurrentLockGroup = currentLock;
                            return(currentLock.addEvent(forEvent));
                        }
                    }
                    finally
                    {
                        Monitor.Exit(objectToLock.LockMutex);
                    }
                }
                else
                {
                    //Failed to get the lock in a reasonable amount of time.
                    return(null);
                }
                //A different thread is using this resource. Wait until that thread is done and try again.
                currentLock.WaitToEnter(startTime, timeout);
            }
        }
Example #6
0
        /// <summary>
        /// Dispose of this lock.
        /// </summary>
        internal void DisposeOf(LockableLock oldLock)
        {
            lock (statusMutex)
            {
                if (LockableLockGroup.CurrentLockGroup != this)
                {
                    throw new SynchronizationLockException("This thread is not currently using this lock group.");
                }

                if (!eventQueue.Remove(oldLock))
                {
                    throw new SynchronizationLockException("This thread is not currently using this lock group.");
                }

                if (eventQueue.Count == 0)
                {
                    LockableLockGroup.CurrentLockGroup = null;
                    Monitor.PulseAll(statusMutex);
                }
            }
        }
Example #7
0
 public LockableLock(ILockHolder holder, LockableLockGroup parentGroup)
 {
     this.holder      = holder;
     this.subPriority = DateTime.UtcNow;
     this.group       = parentGroup;
 }
Example #8
0
        /// <summary>
        /// Get the lock for the requested resource. This may block if the resource is locked by another thread.
        /// This will avoid deadlocks in case of multiple threads locking the same resources; one thread will allow
        /// another thread to take its resources and interrupt it depending on event priority.
        /// </summary>
        /// <param name="resource">Resource to get the lock on</param>
        /// <param name="newLock">Returned lock that represents the event's lock on this resource.</param>
        internal void AddResource(ILockable resource, LockableLock newLock)
        {
            LockableLockGroup otherGroup;

            //LockableLock currentEvent = eventQueue[eventQueue.Count - 1];
restartAddResource:
            LockableLock highestPriority = null;
            bool wakeHighestPriority = false;

            //Check if we can trivially get the lock
            lock (resource.LockMutex)
            {
                otherGroup = resource.CurrentLock;
                if (otherGroup == null || otherGroup.eventQueue.Count == 0)
                {
                    resource.CurrentLock = this;
                    return;
                }
            }
            //Check if we already have the lock
            if (this.contains(otherGroup, false))
            {
                //Already have the lock somewhere. Make sure *this* event also has it marked as being used.
                addToLockableLock(newLock, otherGroup);
                return;
            }

            //Check if we're in a deadlock.
            List <LockableLockGroup> otherGroupQueue;

            lock (otherGroup.statusMutex)
            {
                if (otherGroup.eventQueue.Count == 0)
                {
                    goto restartAddResource;
                }
                otherGroupQueue = new List <LockableLockGroup>();
                otherGroupQueue.Add(otherGroup);
            }

            lock (this.statusMutex)
            {
                this.waitingOn = otherGroupQueue[0];
                this.willWait  = true;
            }
            try
            {
                lock (otherGroup.statusMutex)
                {
                    while (otherGroup.waitingOn == null)
                    {
                        if (otherGroup.eventQueue.Count == 0)
                        {
                            goto restartAddResource;
                        }
                        //Other group is active. Have to wait for that group first.
                        Monitor.Wait(otherGroup.statusMutex);
                    }
                    if (otherGroup.eventQueue.Count == 0)
                    {
                        goto restartAddResource;
                    }
                    highestPriority = this.highestPriority();
                    otherGroupQueue.Add(otherGroup.waitingOn);
                    highestPriority = chooseHighestPriority(highestPriority, otherGroup);
                }
                //Waiting on a lock that is not active. Check to see what we should do.
                while (true)
                {
                    LockableLockGroup nextGroup = otherGroupQueue[otherGroupQueue.Count - 1];
                    lock (nextGroup.statusMutex)
                    {
                        //Make sure lock is up to date.
                        if (nextGroup.eventQueue.Count == 0)
                        {
                            //This event is out of date, meaning there is an active thread. We can wait until the group we're waiting
                            //on finishes, or another thread tells us we're the highest priority thread.
                            break;
                        }
                        //Lock isn't out of date. Check which group this is.
                        else if (this.contains(nextGroup, true))
                        {
                            //There is a loop/deadlock, eventually otherGroup is waiting on this group.
                            if (this.contains(highestPriority.group, true))
                            {
                                //We are the highest priority thread found. We get to take a new subgroup and continue getting locks.
                                goto setupSubgroup;
                            }
                            //else wake up the highestPriority thread.
                            wakeHighestPriority = true;
                            break;
                        }
                        LockableLockGroup nextNextGroup = nextGroup.waitingOn;
                        if (nextNextGroup == null)
                        {
                            //Not currently in a deadlock. Continue and wait on otherGroup.
                            break;
                        }
                        foreach (LockableLockGroup otherWaitingGroup in otherGroupQueue)
                        {
                            if (otherWaitingGroup == nextNextGroup)
                            {
                                //There's a circular loop, but this thread's not in it. Consider it the same as an active thread
                                break;
                            }
                        }
                        //This group is also waiting on a thread. Check the next event/group it is waiting on.
                        highestPriority = chooseHighestPriority(highestPriority, nextGroup);
                        otherGroupQueue.Add(nextGroup.waitingOn);
                        continue;
                    }
                }

                //Need to wait for another thread.
                if (wakeHighestPriority)
                {
                    //We're in a deadlock and not the highest priority thread. Make sure the highest priority thread is woken up.
                    object mutex;
                    lock (highestPriority.group.statusMutex)
                    {
                        //Tell that thread to wake up
                        highestPriority.group.willWait = false;
                        mutex = highestPriority.group.waitingOn?.statusMutex;
                    }

                    // If this is out of date, the highest priority thread already did stuff and we didn't need to wake it up anyways.
                    if (mutex != null)
                    {
                        lock (mutex)
                        {
                            Monitor.PulseAll(mutex);
                        }
                    }
                    wakeHighestPriority = false;
                }
                lock (otherGroup.statusMutex)
                {
                    //This isn't the lock for willWait, but the thread that would wake this thread up would clear willWait, then try to get this same lock, then wake it up, so it works out.
                    if (willWait && otherGroup.eventQueue.Count > 0)
                    {
                        Monitor.Wait(otherGroup.statusMutex);
                    }
                }
                goto restartAddResource;
            }
            finally
            {
                lock (this.statusMutex)
                {
                    this.waitingOn = null;
                    this.willWait  = false;
                }
            }
setupSubgroup:
            //This is the highest priority thread. Setup subgroups.
            addToLockableLock(newLock, otherGroup);
            return;
        }