kernel/eka/euser/us_rwlock.cpp
changeset 0 a41df078684a
equal deleted inserted replaced
-1:000000000000 0:a41df078684a
       
     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