os/kernelhwsrv/kernel/eka/euser/us_rwlock.cpp
author sl@SLION-WIN7.fritz.box
Fri, 15 Jun 2012 03:10:57 +0200
changeset 0 bde4ae8d615e
permissions -rw-r--r--
First public contribution.
     1 // Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
     2 // All rights reserved.
     3 // This component and the accompanying materials are made available
     4 // under the terms of the License "Eclipse Public License v1.0"
     5 // which accompanies this distribution, and is available
     6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
     7 //
     8 // Initial Contributors:
     9 // Nokia Corporation - initial contribution.
    10 //
    11 // Contributors:
    12 //
    13 // Description:
    14 // e32\euser\us_rwlock.cpp
    15 // 
    16 //
    17 
    18 
    19 #include "us_std.h"
    20 #include <e32atomics.h>
    21 
    22 const TInt KReadersIndex				= 0;
    23 const TInt KWriterIndex					= 1;
    24 const TInt KReadersPendingIndex			= 2;
    25 const TInt KWritersPendingIndex			= 3;
    26 const TUint64 KReaderValue				= UI64LIT(0x0000000000000001);
    27 const TUint64 KWriterValue				= UI64LIT(0x0000000000010000);
    28 const TUint64 KReaderPendingValue		= UI64LIT(0x0000000100000000);
    29 const TUint64 KWriterPendingValue		= UI64LIT(0x0001000000000000);
    30 const TUint64 KReadersMask				= UI64LIT(0x000000000000ffff);
    31 const TUint64 KWriterMask				= KWriterValue;
    32 const TUint64 KReadersOrWritersMask		= KReadersMask | KWriterMask;
    33 const TUint64 KReadersPendingClearMask	= UI64LIT(0xffff0000ffffffff);
    34 
    35 /**
    36 Initialise a read-write lock object.
    37 @param		aPriority		Type of priority to use - see RReadWriteLockPriority::TReadWriteLockPriority
    38 @return		KErrNone		Instance successfully created
    39 			Otherwise an error returned by RSemaphore::CreateLocal
    40 @panic		EReadWriteLockInvalidPriority if aPriority is not valid.
    41 */
    42 EXPORT_C TInt RReadWriteLock::CreateLocal(TReadWriteLockPriority aPriority)
    43 	{
    44 	__ASSERT_ALWAYS(aPriority >= EWriterPriority && aPriority <= EReaderPriority, Panic(EReadWriteLockInvalidPriority));
    45 
    46 	iPriority = aPriority;
    47 	iValues = 0;
    48 #ifdef _DEBUG
    49 		iSpare[0] = 0; // Keep a rough track of writer starvation
    50 #endif
    51 
    52 	TInt ret = iReaderSem.CreateLocal(0, EOwnerProcess);
    53 	if (ret == KErrNone)
    54 		ret = iWriterSem.CreateLocal(0, EOwnerProcess);
    55 	if (ret != KErrNone)
    56 		iReaderSem.Close();
    57 
    58 	return ret;
    59 	}
    60 
    61 /**
    62 Close a read-write lock object, releasing the associated semaphores.
    63 @panic		EReadWriteLockStillPending if there are any outstanding clients or pending clients
    64 */
    65 EXPORT_C void RReadWriteLock::Close()
    66 	{
    67 	__ASSERT_ALWAYS(iValues == 0, Panic(EReadWriteLockStillPending));
    68 
    69 	iReaderSem.Close();
    70 	iWriterSem.Close();
    71 	}
    72 
    73 /**
    74 Ask for a read lock. Will be granted if:
    75 	1) No-one else currently holds the lock or
    76 	2) Only readers hold the lock and:
    77 		a) There are no pending writers or
    78 		b) The priority is for readers.
    79 Otherwise this function blocks until the lock becomes available to it.
    80 Please note that ReadLock() is not re-entrant - calling it a second time without releasing the first lock
    81 runs the risk of being blocked and risking a deadlock situation.
    82 @panic		EReadWriteLockTooManyClients if the resulting number of readers or pending readers exceeds EReadWriteLockClientCategoryLimit
    83 */
    84 EXPORT_C void RReadWriteLock::ReadLock()
    85 	{
    86 	TBool blocked;
    87 	TUint64 initialValues;
    88 	TUint16* indexedValues = (TUint16*)&initialValues;
    89 
    90 	do	{
    91 		initialValues = iValues;
    92 
    93 		if (indexedValues[KWriterIndex] > 0 ||
    94 			(iPriority != EReaderPriority && indexedValues[KWritersPendingIndex] > 0))
    95 			{
    96 			__ASSERT_ALWAYS(indexedValues[KReadersPendingIndex] < KMaxTUint16, Panic(EReadWriteLockTooManyClients));
    97 			blocked = ETrue;
    98 			}
    99 		else
   100 			{
   101 			__ASSERT_ALWAYS(indexedValues[KReadersIndex] < KMaxTUint16, Panic(EReadWriteLockTooManyClients));
   102 			blocked = EFalse;
   103 			}
   104 		}
   105 	while (!__e32_atomic_cas_rel64(&iValues, &initialValues, initialValues + (blocked ? KReaderPendingValue : KReaderValue)));
   106 
   107 	if (blocked)
   108 		iReaderSem.Wait();
   109 	}
   110 
   111 /**
   112 Ask for a write lock. Will be granted if no-one else currently holds the lock.
   113 Otherwise this function blocks until the lock becomes available to it.
   114 Only one writer can hold the lock at one time. No readers can hold the lock while a writer has it.
   115 Please note that WriteLock() is not re-entrant - calling it a second time without releasing the first lock
   116 will block and cause a deadlock situation.
   117 @panic		EReadWriteLockTooManyClients if the resulting number of pending writers exceeds EReadWriteLockClientCategoryLimit
   118 */
   119 EXPORT_C void RReadWriteLock::WriteLock()
   120 	{
   121 	TBool blocked;
   122 	TUint64 initialValues;
   123 	TUint16* indexedValues = (TUint16*)&initialValues;
   124 
   125 	do	{
   126 		initialValues = iValues;
   127 
   128 		if (initialValues & KReadersOrWritersMask)
   129 			{
   130 			__ASSERT_ALWAYS(indexedValues[KWritersPendingIndex] < KMaxTUint16, Panic(EReadWriteLockTooManyClients));
   131 			blocked = ETrue;
   132 			}
   133 		else
   134 			{
   135 			blocked = EFalse;
   136 			}
   137 		}
   138 	while (!__e32_atomic_cas_rel64(&iValues, &initialValues, initialValues + (blocked ? KWriterPendingValue : KWriterValue)));
   139 
   140 	if (blocked)
   141 		iWriterSem.Wait();
   142 	}
   143 
   144 /**
   145 Ask for a read lock without blocking.
   146 @return		ETrue - lock granted
   147 			EFalse - failed to obtain the lock
   148 @panic		EReadWriteLockTooManyClients if the resulting number of readers exceeds EReadWriteLockClientCategoryLimit
   149 @see		ReadLock()
   150 */
   151 EXPORT_C TBool RReadWriteLock::TryReadLock()
   152 	{
   153 	TUint64 initialValues;
   154 	TUint16* indexedValues = (TUint16*)&initialValues;
   155 
   156 	do	{
   157 		initialValues = iValues;
   158 
   159 		if (indexedValues[KWriterIndex] > 0 ||
   160 			(iPriority != EReaderPriority && indexedValues[KWritersPendingIndex] > 0))
   161 			return EFalse;
   162 
   163 		__ASSERT_ALWAYS(indexedValues[KReadersIndex] < KMaxTUint16, Panic(EReadWriteLockTooManyClients));
   164 		}
   165 	while (!__e32_atomic_cas_rel64(&iValues, &initialValues, initialValues + KReaderValue));
   166 
   167 	return ETrue;
   168 	}
   169 
   170 /**
   171 Ask for a write lock without blocking.
   172 @return		ETrue - lock granted
   173 			EFalse - failed to obtain the lock
   174 @see		WriteLock()
   175 */
   176 EXPORT_C TBool RReadWriteLock::TryWriteLock()
   177 	{
   178 	TUint64 initialValues;
   179 
   180 	do	{
   181 		initialValues = iValues;
   182 
   183 		if (initialValues & KReadersOrWritersMask)
   184 			return EFalse;
   185 		}
   186 	while (!__e32_atomic_cas_rel64(&iValues, &initialValues, initialValues + KWriterValue));
   187 
   188 	return ETrue;
   189 	}
   190 
   191 /**
   192 Tries to atomically release a read lock and gain a write lock.
   193 This function will succeed if:
   194 	- This is the only reader and
   195 		- There are no pending writers or
   196 		- The priority is reader
   197 @return		ETrue - write lock granted
   198 			EFalse - failed to obtain a write lock, read lock retained
   199 @panic		EReadWriteLockBadLockState if the read lock is not currently held
   200 */
   201 EXPORT_C TBool RReadWriteLock::TryUpgradeReadLock()
   202 	{
   203 	__ASSERT_ALWAYS((iValues & KReadersMask) != 0, Panic(EReadWriteLockBadLockState)); // Check we actually hold a read lock
   204 	__ASSERT_DEBUG((iValues & KWriterMask) == 0, Panic(EReadWriteLockBadLockState)); // Check we don't hold a write lock - shouldn't be possible
   205 
   206 	TUint64 initialValues;
   207 	TUint16* indexedValues = (TUint16*)&initialValues;
   208 
   209 	do	{
   210 		initialValues = iValues;
   211 
   212 		if (indexedValues[KReadersIndex] > 1 ||
   213 			(iPriority != EReaderPriority && indexedValues[KWritersPendingIndex] > 0))
   214               return EFalse;
   215 		}
   216 	while (!__e32_atomic_cas_acq64(&iValues, &initialValues, initialValues - KReaderValue + KWriterValue));
   217 
   218 	return ETrue;
   219 	}
   220 
   221 /**
   222 Atomically releases a held write lock and gains a read lock. Also unblocks any
   223 pending readers if:
   224 	- Priority is EPriorityReader or
   225 	- There are no pending writers
   226 This function can not fail, so it does not return anything.
   227 @panic		EReadWriteLockBadLockState if the lock is not currently held
   228 */
   229 EXPORT_C void RReadWriteLock::DowngradeWriteLock()
   230 	{
   231 	__ASSERT_ALWAYS((iValues & KWriterMask) == KWriterValue, Panic(EReadWriteLockBadLockState)); // Check we actually hold a write lock
   232 	__ASSERT_DEBUG((iValues & KReadersMask) == 0, Panic(EReadWriteLockBadLockState)); // Check we don't hold a read lock - shouldn't be possible
   233 
   234 	TUint unlockReaders;
   235 	TUint64 initialValues;
   236 	TUint16* indexedValues = (TUint16*)&initialValues;
   237 	TUint64 newValues;
   238 
   239 	do	{
   240 		unlockReaders = 0;
   241 		initialValues = iValues;
   242 		newValues = initialValues - KWriterValue + KReaderValue; // Clear current write lock flag and add a read lock
   243 
   244 		if (indexedValues[KReadersPendingIndex] > 0 &&
   245 			(indexedValues[KWritersPendingIndex] == 0 || iPriority == EReaderPriority)) // Release any other pending readers
   246 			{
   247 			unlockReaders = indexedValues[KReadersPendingIndex];
   248 			newValues &= KReadersPendingClearMask; // Clear pending readers
   249 
   250 			if (unlockReaders == KMaxTUint16) // Put a pending reader back to avoid overflow in the readers field
   251 				{
   252 				unlockReaders--;
   253 				newValues += KReaderPendingValue;
   254 				}
   255 
   256 			newValues += unlockReaders;
   257 			}
   258 		}
   259 	while (!__e32_atomic_cas_acq64(&iValues, &initialValues, newValues));
   260 
   261 	if (unlockReaders > 0)
   262 		iReaderSem.Signal(unlockReaders);
   263 	}
   264 
   265 /**
   266 Releases a held read or write lock. If no-one else holds this lock (ie other
   267 readers) then this will unblock one or more pending clients based on the priority:
   268 	EAlternatePriority	- If a read lock is being released then:
   269 							- Give the lock to the first pending writer, if there is one
   270 							- Else give the lock to all pending readers, if there are any
   271 						- If a write lock is being released then:
   272 							- If there are pending readers:
   273 								- If there are pending writers then unblock one pending reader
   274 								- Else if there are no pending writers then unblock all pending readers
   275 							- Else unblock one pending writer, if there is one
   276 	EReaderPriority		- Unblock all pending readers. If none then unblock one pending writer, if there is one
   277 	EWriterPriority		- Unblock one pending writer, if there is one. If none then unblock any and all pending readers
   278 @panic		EReadWriteLockBadLockState if the lock is not currently held
   279 */
   280 EXPORT_C void RReadWriteLock::Unlock()
   281 	{
   282 	__ASSERT_ALWAYS((iValues & KReadersOrWritersMask) != 0, Panic(EReadWriteLockBadLockState)); // Check we actually hold a lock
   283 	__ASSERT_DEBUG((iValues & KReadersOrWritersMask) <= KWriterValue, Panic(EReadWriteLockBadLockState)); // Check we don't hold a read lock and a write lock at the same time - shouldn't be possible
   284 
   285 	TInt unlockClients = 0;
   286 
   287 	switch (iPriority)
   288 		{
   289 	case EWriterPriority:
   290 		unlockClients = UnlockWriter(); break;
   291 	case EAlternatePriority:
   292 		unlockClients = UnlockAlternate(); break;
   293 	default: // EReaderPriority:
   294 		unlockClients = UnlockReader(); break;
   295 		};
   296 
   297 	if (unlockClients == -1)
   298 		{
   299 #ifdef _DEBUG
   300 		iSpare[0] = 0; // Keep a rough track of writer starvation
   301 #endif
   302 		iWriterSem.Signal();
   303 		}
   304 	else if (unlockClients > 0)
   305 		{
   306 #ifdef _DEBUG
   307 		const TUint64 KWritersPendingMask = UI64LIT(0xffff000000000000);
   308 		if (iValues & KWritersPendingMask)
   309 			iSpare[0]++; // Keep a rough track of writer starvation
   310 		if (iSpare[0] > 1000)
   311 			Panic(EReadWriteLockWriterStarvation);
   312 #endif
   313 		iReaderSem.Signal(unlockClients);
   314 		}
   315 	}
   316 
   317 TInt RReadWriteLock::UnlockWriter()
   318 	{
   319 	TUint64 initialValues;
   320 	TUint16* indexedValues = (TUint16*)&initialValues;
   321 	TUint64 newValues;
   322 	TInt unlockClients;
   323 
   324 	do	{
   325 		unlockClients = 0;
   326 		initialValues = iValues;
   327 		newValues = initialValues - (indexedValues[KReadersIndex] > 0 ? KReaderValue : KWriterValue); // Clear current lock flag
   328 
   329 		if ((newValues & KReadersOrWritersMask) == 0) // No longer locked - release someone else
   330 			{
   331 			if (indexedValues[KWritersPendingIndex] > 0) // Release a writer
   332 				{
   333 				unlockClients = -1;
   334 				newValues -= KWriterPendingValue;
   335 				newValues += KWriterValue;
   336 				}
   337 			else if (indexedValues[KReadersPendingIndex] > 0) // Release all pending readers
   338 				{
   339 				unlockClients = indexedValues[KReadersPendingIndex];
   340 				newValues &= KReadersPendingClearMask; // Clear pending readers
   341 				newValues += unlockClients;
   342 				}
   343 			}
   344 		}
   345 	while (!__e32_atomic_cas_acq64(&iValues, &initialValues, newValues));
   346 
   347 	return unlockClients;
   348 	}
   349 
   350 TInt RReadWriteLock::UnlockAlternate()
   351 	{
   352 	TUint64 initialValues;
   353 	TUint16* indexedValues = (TUint16*)&initialValues;
   354 	TUint64 newValues;
   355 	TInt unlockClients;
   356 
   357 	do	{
   358 		unlockClients = 0;
   359 		initialValues = iValues;
   360 		newValues = initialValues - (indexedValues[KReadersIndex] > 0 ? KReaderValue : KWriterValue); // Clear current lock flag
   361 
   362 		if ((newValues & KReadersOrWritersMask) == 0) // No longer locked - release someone else
   363 			{
   364 			if (indexedValues[KWritersPendingIndex] > 0 &&
   365 				(indexedValues[KReadersIndex] > 0 || indexedValues[KReadersPendingIndex] == 0)) // Release a writer if there is one and either this is a read unlock or there are no readers pending
   366 				{
   367 				unlockClients = -1;
   368 				newValues -= KWriterPendingValue;
   369 				newValues += KWriterValue;
   370 				}
   371 			else if (indexedValues[KReadersPendingIndex] > 0) // Release one or more readers
   372 				{
   373 				if (indexedValues[KWritersPendingIndex] > 0) // Just one because there are pending writers
   374 					{
   375 					unlockClients = 1;
   376 					newValues -= KReaderPendingValue;
   377 					newValues += KReaderValue;
   378 					}
   379 				else // All of them
   380 					{
   381 					unlockClients = indexedValues[KReadersPendingIndex];
   382 					newValues &= KReadersPendingClearMask; // Clear pending readers
   383 					newValues += unlockClients;
   384 					}
   385 				}
   386 
   387 			}
   388 		}
   389 	while (!__e32_atomic_cas_acq64(&iValues, &initialValues, newValues));
   390 
   391 	return unlockClients;
   392 	}
   393 
   394 TInt RReadWriteLock::UnlockReader()
   395 	{
   396 	TUint64 initialValues;
   397 	TUint16* indexedValues = (TUint16*)&initialValues;
   398 	TUint64 newValues;
   399 	TInt unlockClients;
   400 
   401 	do	{
   402 		unlockClients = 0;
   403 		initialValues = iValues;
   404 		newValues = initialValues - (indexedValues[KReadersIndex] > 0 ? KReaderValue : KWriterValue); // Clear current lock flag
   405 
   406 		if ((newValues & KReadersOrWritersMask) == 0) // No longer locked - release someone else
   407 			{
   408 			if (indexedValues[KReadersPendingIndex] > 0) // Release all pending readers
   409 				{
   410 				unlockClients = indexedValues[KReadersPendingIndex];
   411 				newValues &= KReadersPendingClearMask; // Clear pending readers
   412 				newValues += unlockClients;
   413 				}
   414 			else if (indexedValues[KWritersPendingIndex] > 0) // Release a writer
   415 				{
   416 				unlockClients = -1;
   417 				newValues -= KWriterPendingValue;
   418 				newValues += KWriterValue;
   419 				}
   420 			}
   421 		}
   422 	while (!__e32_atomic_cas_acq64(&iValues, &initialValues, newValues));
   423 
   424 	return unlockClients;
   425 	}
   426