//
// System.Threading.ReaderWriterLockSlim.cs
//
// Authors:
//   Miguel de Icaza (miguel@novell.com) 
//   Dick Porter (dick@ximian.com)
//   Jackson Harper (jackson@ximian.com)
//   Lluis Sanchez Gual (lluis@ximian.com)
//   Marek Safar (marek.safar@gmail.com)
//
// Copyright 2004-2008 Novell, Inc (http://www.novell.com)
// Copyright 2003, Ximian, Inc.
//
// NoRecursion code based on the blog post from Vance Morrison:
//   http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
//
// Recursion code based on Mono's implementation of ReaderWriterLock.
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Permissions;
using System.Diagnostics;
using System.Threading;

namespace System.Threading {

	//
	// This implementation is based on the light-weight
	// Reader/Writer lock sample from Vance Morrison's blog:
	//
	// http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
	//
	// And in Mono's ReaderWriterLock
	//
	[HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
	[HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
	public class ReaderWriterLockSlim : IDisposable {
		sealed class LockDetails
		{
			public int ThreadId;
			public int ReadLocks;
		}

		// Are we on a multiprocessor?
		static readonly bool smp;
		
		// Lock specifiation for myLock:  This lock protects exactly the local fields associted
		// instance of MyReaderWriterLock.  It does NOT protect the memory associted with the
		// the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
		int myLock;

		// Who owns the lock owners > 0 => readers
		// owners = -1 means there is one writer, Owners must be >= -1.  
		int owners;
		Thread upgradable_thread;
		Thread write_thread;
		
		// These variables allow use to avoid Setting events (which is expensive) if we don't have to. 
		uint numWriteWaiters;        // maximum number of threads that can be doing a WaitOne on the writeEvent 
		uint numReadWaiters;         // maximum number of threads that can be doing a WaitOne on the readEvent
		uint numUpgradeWaiters;      // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1). 
		
		// conditions we wait on. 
		EventWaitHandle writeEvent;    // threads waiting to aquire a write lock go here.
		EventWaitHandle readEvent;     // threads waiting to aquire a read lock go here (will be released in bulk)
		EventWaitHandle upgradeEvent;  // thread waiting to upgrade a read lock to a write lock go here (at most one)

		//int lock_owner;

		// Only set if we are a recursive lock
		//Dictionary<int,int> reader_locks;

		readonly LockRecursionPolicy recursionPolicy;
		LockDetails[] read_locks = new LockDetails [8];
		
		static ReaderWriterLockSlim ()
		{
			smp = Environment.ProcessorCount > 1;
		}
		
		public ReaderWriterLockSlim ()
		{
			// NoRecursion (0) is the default value
		}

		public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
		{
			this.recursionPolicy = recursionPolicy;
			
			if (recursionPolicy != LockRecursionPolicy.NoRecursion){
				//reader_locks = new Dictionary<int,int> ();
				throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
			}
		}

		public void EnterReadLock ()
		{
			TryEnterReadLock (-1);
		}

		public bool TryEnterReadLock (int millisecondsTimeout)
		{
			if (millisecondsTimeout < Timeout.Infinite)
				throw new ArgumentOutOfRangeException ("millisecondsTimeout");
			
			if (read_locks == null)
				throw new ObjectDisposedException (null);

			if (Thread.CurrentThread == write_thread)
				throw new LockRecursionException ("Read lock cannot be acquired while write lock is held");

			EnterMyLock ();

			LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, true);
			if (ld.ReadLocks != 0) {
				ExitMyLock ();
				throw new LockRecursionException ("Recursive read lock can only be aquired in SupportsRecursion mode");
			}
			++ld.ReadLocks;
			
			while (true){
				// Easy case, no contention
				// owners >= 0 means there might be readers (but no writer)
				if (owners >= 0 && numWriteWaiters == 0){
					owners++;
					break;
				}
				
				// If the request is to probe.
				if (millisecondsTimeout == 0){
					ExitMyLock ();
					return false;
				}

				// We need to wait.  Mark that we have waiters and wait.  
				if (readEvent == null) {
					LazyCreateEvent (ref readEvent, false);
					// since we left the lock, start over. 
					continue;
				}

				if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
					return false;
			}
			ExitMyLock ();
			
			return true;
		}

		public bool TryEnterReadLock (TimeSpan timeout)
		{
			return TryEnterReadLock (CheckTimeout (timeout));
		}

		//
		// TODO: What to do if we are releasing a ReadLock and we do not own it?
		//
		public void ExitReadLock ()
		{
			EnterMyLock ();

			if (owners < 1) {
				ExitMyLock ();
				throw new SynchronizationLockException ("Releasing lock and no read lock taken");
			}

			--owners;
			--GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false).ReadLocks;

			ExitAndWakeUpAppropriateWaiters ();
		}

		public void EnterWriteLock ()
		{
			TryEnterWriteLock (-1);
		}
		
		public bool TryEnterWriteLock (int millisecondsTimeout)
		{
			if (millisecondsTimeout < Timeout.Infinite)
				throw new ArgumentOutOfRangeException ("millisecondsTimeout");

			if (read_locks == null)
				throw new ObjectDisposedException (null);

			if (IsWriteLockHeld)
				throw new LockRecursionException ();

			EnterMyLock ();

			LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
			if (ld != null && ld.ReadLocks > 0) {
				ExitMyLock ();
				throw new LockRecursionException ("Write lock cannot be acquired while read lock is held");
			}

			while (true){
				// There is no contention, we are done
				if (owners == 0){
					// Indicate that we have a writer
					owners = -1;
					write_thread = Thread.CurrentThread;
					break;
				}

				// If we are the thread that took the Upgradable read lock
				if (owners == 1 && upgradable_thread == Thread.CurrentThread){
					owners = -1;
					write_thread = Thread.CurrentThread;
					break;
				}

				// If the request is to probe.
				if (millisecondsTimeout == 0){
					ExitMyLock ();
					return false;
				}

				// We need to wait, figure out what kind of waiting.
				
				if (upgradable_thread == Thread.CurrentThread){
					// We are the upgradable thread, register our interest.
					
					if (upgradeEvent == null){
						LazyCreateEvent (ref upgradeEvent, false);

						// since we left the lock, start over.
						continue;
					}

					if (numUpgradeWaiters > 0){
						ExitMyLock ();
						throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
					}
					
					if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
						return false;
				} else {
					if (writeEvent == null){
						LazyCreateEvent (ref writeEvent, true);

						// since we left the lock, retry
						continue;
					}
					if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
						return false;
				}
			}

			Debug.Assert (owners == -1, "Owners is not -1");
			ExitMyLock ();
			return true;
		}

		public bool TryEnterWriteLock (TimeSpan timeout)
		{
			return TryEnterWriteLock (CheckTimeout (timeout));
		}

		public void ExitWriteLock ()
		{
			EnterMyLock ();

			if (owners != -1) {
				ExitMyLock ();
				throw new SynchronizationLockException ("Calling ExitWriterLock when no write lock is held");
			}

			//Debug.Assert (numUpgradeWaiters > 0);
			write_thread = upgradable_thread = null;
			owners = 0;
			ExitAndWakeUpAppropriateWaiters ();
		}

		public void EnterUpgradeableReadLock ()
		{
			TryEnterUpgradeableReadLock (-1);
		}

		//
		// Taking the Upgradable read lock is like taking a read lock
		// but we limit it to a single upgradable at a time.
		//
		public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
		{
			if (millisecondsTimeout < Timeout.Infinite)
				throw new ArgumentOutOfRangeException ("millisecondsTimeout");

			if (read_locks == null)
				throw new ObjectDisposedException (null);

			if (IsUpgradeableReadLockHeld)
				throw new LockRecursionException ();

			if (IsWriteLockHeld)
				throw new LockRecursionException ();

			EnterMyLock ();
			while (true){
				if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
					owners++;
					upgradable_thread = Thread.CurrentThread;
					break;
				}

				// If the request is to probe
				if (millisecondsTimeout == 0){
					ExitMyLock ();
					return false;
				}

				if (readEvent == null){
					LazyCreateEvent (ref readEvent, false);
					// since we left the lock, start over.
					continue;
				}

				if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
					return false;
			}

			ExitMyLock ();
			return true;
		}

		public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
		{
			return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
		}
	       
		public void ExitUpgradeableReadLock ()
		{
			EnterMyLock ();
			Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
			--owners;
			upgradable_thread = null;
			ExitAndWakeUpAppropriateWaiters ();
		}

		public void Dispose ()
		{
			read_locks = null;
		}

		public bool IsReadLockHeld {
			get { return RecursiveReadCount != 0; }
		}
		
		public bool IsWriteLockHeld {
			get { return RecursiveWriteCount != 0; }
		}
		
		public bool IsUpgradeableReadLockHeld {
			get { return RecursiveUpgradeCount != 0; }
		}

		public int CurrentReadCount {
			get { return owners & 0xFFFFFFF; }
		}
		
		public int RecursiveReadCount {
			get {
				EnterMyLock ();
				LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
				int count = ld == null ? 0 : ld.ReadLocks;
				ExitMyLock ();
				return count;
			}
		}

		public int RecursiveUpgradeCount {
			get { return upgradable_thread == Thread.CurrentThread ? 1 : 0; }
		}

		public int RecursiveWriteCount {
			get { return write_thread == Thread.CurrentThread ? 1 : 0; }
		}

		public int WaitingReadCount {
			get { return (int) numReadWaiters; }
		}

		public int WaitingUpgradeCount {
			get { return (int) numUpgradeWaiters; }
		}

		public int WaitingWriteCount {
			get { return (int) numWriteWaiters; }
		}

		public LockRecursionPolicy RecursionPolicy {
			get { return recursionPolicy; }
		}
		
#region Private methods
		void EnterMyLock ()
		{
			if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
				EnterMyLockSpin ();
		}

		void EnterMyLockSpin ()
		{

			for (int i = 0; ;i++) {
				if (i < 3 && smp)
					Thread.SpinWait (20);    // Wait a few dozen instructions to let another processor release lock. 
				else 
					Thread.Sleep (0);        // Give up my quantum.  

				if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
					return;
			}
		}

		void ExitMyLock()
		{
			Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
			myLock = 0;
		}

		bool MyLockHeld { get { return myLock != 0; } }

		/// <summary>
		/// Determines the appropriate events to set, leaves the locks, and sets the events. 
		/// </summary>
		private void ExitAndWakeUpAppropriateWaiters()
		{
			Debug.Assert (MyLockHeld);

			// First a writing thread waiting on being upgraded
			if (owners == 1 && numUpgradeWaiters != 0){
				// Exit before signaling to improve efficiency (wakee will need the lock)
				ExitMyLock ();
				// release all upgraders (however there can be at most one). 
				upgradeEvent.Set ();
				//
				// TODO: What does the following comment mean?
				// two threads upgrading is a guarenteed deadlock, so we throw in that case. 
			} else if (owners == 0 && numWriteWaiters > 0) {
				// Exit before signaling to improve efficiency (wakee will need the lock)
				ExitMyLock ();
				// release one writer. 
				writeEvent.Set ();
			}
			else if (owners >= 0 && numReadWaiters != 0) {
				// Exit before signaling to improve efficiency (wakee will need the lock)
				ExitMyLock ();
				// release all readers.
				readEvent.Set();
			} else
				ExitMyLock();
		}

		/// <summary>
		/// A routine for lazily creating a event outside the lock (so if errors
		/// happen they are outside the lock and that we don't do much work
		/// while holding a spin lock).  If all goes well, reenter the lock and
		/// set 'waitEvent' 
		/// </summary>
		void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
		{
			Debug.Assert (MyLockHeld);
			Debug.Assert (waitEvent == null);
			
			ExitMyLock ();
			EventWaitHandle newEvent;
			if (makeAutoResetEvent) 
				newEvent = new AutoResetEvent (false);
			else 
				newEvent = new ManualResetEvent (false);

			EnterMyLock ();

			// maybe someone snuck in. 
			if (waitEvent == null)
				waitEvent = newEvent;
		}

		/// <summary>
		/// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.  
		/// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
		/// </summary>
		bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
		{
			Debug.Assert (MyLockHeld);

			waitEvent.Reset ();
			numWaiters++;

			bool waitSuccessful = false;

			// Do the wait outside of any lock 
			ExitMyLock();      
			try {
				waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
			} finally {
				EnterMyLock ();
				--numWaiters;
				if (!waitSuccessful)
					ExitMyLock ();
			}
			return waitSuccessful;
		}
		
		static int CheckTimeout (TimeSpan timeout)
		{
			try {
				return checked((int) timeout.TotalMilliseconds);
			} catch (System.OverflowException) {
				throw new ArgumentOutOfRangeException ("timeout");				
			}
		}

		LockDetails GetReadLockDetails (int threadId, bool create)
		{
			int i;
			LockDetails ld;
			for (i = 0; i < read_locks.Length; ++i) {
				ld = read_locks [i];
				if (ld == null)
					break;

				if (ld.ThreadId == threadId)
					return ld;
			}

			if (!create)
				return null;

			if (i == read_locks.Length)
				Array.Resize (ref read_locks, read_locks.Length * 2);
				
			ld = read_locks [i] = new LockDetails ();
			ld.ThreadId = threadId;
			return ld;
		}
#endregion
	}
}
