| 1 |
|
|---|
| 2 | /*
|
|---|
| 3 | *@@sourcefile semaphores.c:
|
|---|
| 4 | * implements read-write semaphores.
|
|---|
| 5 | *
|
|---|
| 6 | * Read-write semaphores are similar to the regular
|
|---|
| 7 | * OS/2 mutex semaphores in that they are used to
|
|---|
| 8 | * serialize access to a resource. See CPREF for an
|
|---|
| 9 | * introduction to mutex semaphores -- do not use
|
|---|
| 10 | * the things in this file if you have never used
|
|---|
| 11 | * regular mutexes.
|
|---|
| 12 | *
|
|---|
| 13 | * Regular mutexes are inefficient though if most
|
|---|
| 14 | * of the access to the protected resource is
|
|---|
| 15 | * read-only. It is not dangerous if several threads
|
|---|
| 16 | * read from the same resource at the same time,
|
|---|
| 17 | * as long as none of the threads actually modifies
|
|---|
| 18 | * the resource. Still, with regular mutexes, a
|
|---|
| 19 | * reading thread will be blocked out while another
|
|---|
| 20 | * thread is reading, which isn't really necessary.
|
|---|
| 21 | *
|
|---|
| 22 | * So read-write mutexes differentiate between read
|
|---|
| 23 | * and write access. After creating a read-write
|
|---|
| 24 | * semaphore using semCreateRWMutex,
|
|---|
| 25 | *
|
|---|
| 26 | * -- to request read access, call semRequestRead
|
|---|
| 27 | * (and semReleaseRead when done); this will
|
|---|
| 28 | * let the thread in as long as no other thread
|
|---|
| 29 | * has write access;
|
|---|
| 30 | *
|
|---|
| 31 | * -- to request write access, call semRequestWrite
|
|---|
| 32 | * (and semReleaseWrite when done); this will
|
|---|
| 33 | * let the thread in _only_ if no other thread
|
|---|
| 34 | * currently has requested either read or write
|
|---|
| 35 | * access.
|
|---|
| 36 | *
|
|---|
| 37 | * In other words, only write access is exclusive as with
|
|---|
| 38 | * regular mutexes.
|
|---|
| 39 | *
|
|---|
| 40 | * This file is new with V0.9.12 (2001-05-24) [umoeller].
|
|---|
| 41 | *
|
|---|
| 42 | * Usage: All PM programs.
|
|---|
| 43 | *
|
|---|
| 44 | * Function prefix:
|
|---|
| 45 | *
|
|---|
| 46 | * -- sem*: semaphore helpers.
|
|---|
| 47 | *
|
|---|
| 48 | *@@added V0.9.12 (2001-05-24) [umoeller]
|
|---|
| 49 | *@@header "helpers\semaphores.h"
|
|---|
| 50 | */
|
|---|
| 51 |
|
|---|
| 52 | #define OS2EMX_PLAIN_CHAR
|
|---|
| 53 | // this is needed for "os2emx.h"; if this is defined,
|
|---|
| 54 | // emx will define PSZ as _signed_ char, otherwise
|
|---|
| 55 | // as unsigned char
|
|---|
| 56 |
|
|---|
| 57 | #define INCL_DOSERRORS
|
|---|
| 58 | #define INCL_DOSSEMAPHORES
|
|---|
| 59 |
|
|---|
| 60 | #define INCL_WINMESSAGEMGR
|
|---|
| 61 | #include <os2.h>
|
|---|
| 62 |
|
|---|
| 63 | #include <stdlib.h>
|
|---|
| 64 |
|
|---|
| 65 | #include "setup.h" // code generation and debugging options
|
|---|
| 66 |
|
|---|
| 67 | #include "helpers\dosh.h"
|
|---|
| 68 | #include "helpers\semaphores.h"
|
|---|
| 69 | #include "helpers\standards.h"
|
|---|
| 70 | #include "helpers\tree.h"
|
|---|
| 71 |
|
|---|
| 72 | #pragma hdrstop
|
|---|
| 73 |
|
|---|
| 74 | /*
|
|---|
| 75 | *@@category: Helpers\Control program helpers\Semaphores
|
|---|
| 76 | * see semaphores.c.
|
|---|
| 77 | */
|
|---|
| 78 |
|
|---|
| 79 | /* ******************************************************************
|
|---|
| 80 | *
|
|---|
| 81 | * Private declarations
|
|---|
| 82 | *
|
|---|
| 83 | ********************************************************************/
|
|---|
| 84 |
|
|---|
| 85 | /*
|
|---|
| 86 | *@@ RWMUTEX:
|
|---|
| 87 | * read-write mutex, as created by
|
|---|
| 88 | * mtxCreateRWMutex.
|
|---|
| 89 | *
|
|---|
| 90 | * The HRW handle is really a PRWMUTEX.
|
|---|
| 91 | */
|
|---|
| 92 |
|
|---|
| 93 | typedef struct _RWMUTEX
|
|---|
| 94 | {
|
|---|
| 95 | ULONG cReaders;
|
|---|
| 96 | // current no. of readers on all threads,
|
|---|
| 97 | // including nested read requests (0 if none)
|
|---|
| 98 | TREE *ReaderThreadsTree;
|
|---|
| 99 | // red-black tree (tree.c) containing a
|
|---|
| 100 | // READERTREENODE for each thread which
|
|---|
| 101 | // ever requested read access; items are
|
|---|
| 102 | // only added, but never removed (for speed)
|
|---|
| 103 | ULONG cReaderThreads;
|
|---|
| 104 | // tree item count
|
|---|
| 105 | // (this is NOT the same as cReaders)
|
|---|
| 106 |
|
|---|
| 107 | ULONG cWriters;
|
|---|
| 108 | // current no. of writers (0 if none);
|
|---|
| 109 | // this will only be > 1 if the same
|
|---|
| 110 | // thread did a nested write request,
|
|---|
| 111 | // since only one thread can ever have
|
|---|
| 112 | // write access at a time
|
|---|
| 113 | ULONG tidWriter;
|
|---|
| 114 | // TID of current writer or 0 if none
|
|---|
| 115 |
|
|---|
| 116 | HEV hevWriterDone;
|
|---|
| 117 | // posted after writers count goes to 0;
|
|---|
| 118 | // semRequestRead blocks on this
|
|---|
| 119 |
|
|---|
| 120 | HEV hevReadersDone;
|
|---|
| 121 | // posted after readers or writers count
|
|---|
| 122 | // goes to 0; semRequestWrite blocks on this
|
|---|
| 123 |
|
|---|
| 124 | } RWMUTEX, *PRWMUTEX;
|
|---|
| 125 |
|
|---|
| 126 | /*
|
|---|
| 127 | *@@ READERTREENODE:
|
|---|
| 128 | * tree item structure which describes
|
|---|
| 129 | * a thread which requested read access.
|
|---|
| 130 | *
|
|---|
| 131 | * These nodes are stored in RWMUTEX.ReadersTree
|
|---|
| 132 | * and only ever allocated, but never removed
|
|---|
| 133 | * from the tree for speed.
|
|---|
| 134 | *
|
|---|
| 135 | * Since TREE.id holds the thread ID, this
|
|---|
| 136 | * tree is sorted by thread IDs.
|
|---|
| 137 | */
|
|---|
| 138 |
|
|---|
| 139 | typedef struct _READERTREENODE
|
|---|
| 140 | {
|
|---|
| 141 | TREE Tree; // tree base struct; "id" member
|
|---|
| 142 | // has the TID
|
|---|
| 143 |
|
|---|
| 144 | ULONG cRequests; // read requests from this thread;
|
|---|
| 145 | // 0 after the last read request
|
|---|
| 146 | // was released (since tree node
|
|---|
| 147 | // won't be freed then)
|
|---|
| 148 |
|
|---|
| 149 | } READERTREENODE, *PREADERTREENODE;
|
|---|
| 150 |
|
|---|
| 151 | /* ******************************************************************
|
|---|
| 152 | *
|
|---|
| 153 | * Global variables
|
|---|
| 154 | *
|
|---|
| 155 | ********************************************************************/
|
|---|
| 156 |
|
|---|
| 157 | static HMTX G_hmtxGlobal = NULLHANDLE;
|
|---|
| 158 |
|
|---|
| 159 | /* ******************************************************************
|
|---|
| 160 | *
|
|---|
| 161 | * Private helpers
|
|---|
| 162 | *
|
|---|
| 163 | ********************************************************************/
|
|---|
| 164 |
|
|---|
| 165 | /*
|
|---|
| 166 | *@@ LockGlobal:
|
|---|
| 167 | *
|
|---|
| 168 | * WARNING: As opposed to most other Lock* functions
|
|---|
| 169 | * I have created, this returns an APIRET.
|
|---|
| 170 | *
|
|---|
| 171 | */
|
|---|
| 172 |
|
|---|
| 173 | static APIRET LockGlobal(VOID)
|
|---|
| 174 | {
|
|---|
| 175 | if (!G_hmtxGlobal)
|
|---|
| 176 | // first call:
|
|---|
| 177 | return (DosCreateMutexSem(NULL,
|
|---|
| 178 | &G_hmtxGlobal,
|
|---|
| 179 | 0,
|
|---|
| 180 | TRUE)); // request!
|
|---|
| 181 |
|
|---|
| 182 | return (WinRequestMutexSem(G_hmtxGlobal, SEM_INDEFINITE_WAIT));
|
|---|
| 183 | }
|
|---|
| 184 |
|
|---|
| 185 | /*
|
|---|
| 186 | *@@ UnlockGlobal:
|
|---|
| 187 | *
|
|---|
| 188 | */
|
|---|
| 189 |
|
|---|
| 190 | static VOID UnlockGlobal(VOID)
|
|---|
| 191 | {
|
|---|
| 192 | DosReleaseMutexSem(G_hmtxGlobal);
|
|---|
| 193 | }
|
|---|
| 194 |
|
|---|
| 195 | /* ******************************************************************
|
|---|
| 196 | *
|
|---|
| 197 | * Public interfaces
|
|---|
| 198 | *
|
|---|
| 199 | ********************************************************************/
|
|---|
| 200 |
|
|---|
| 201 | /*
|
|---|
| 202 | *@@ semCreateRWMutex:
|
|---|
| 203 | * creates a read-write mutex.
|
|---|
| 204 | *
|
|---|
| 205 | * If this returns NO_ERROR, a new RWMUTEX
|
|---|
| 206 | * has been created in *ppMutex. You must
|
|---|
| 207 | * use semDeleteRWMutex to free it again.
|
|---|
| 208 | */
|
|---|
| 209 |
|
|---|
| 210 | APIRET semCreateRWMutex(PHRW phrw)
|
|---|
| 211 | {
|
|---|
| 212 | APIRET arc = NO_ERROR;
|
|---|
| 213 |
|
|---|
| 214 | if (!(arc = LockGlobal()))
|
|---|
| 215 | {
|
|---|
| 216 | PRWMUTEX pMutex = NEW(RWMUTEX);
|
|---|
| 217 | if (!pMutex)
|
|---|
| 218 | arc = ERROR_NOT_ENOUGH_MEMORY;
|
|---|
| 219 | else
|
|---|
| 220 | {
|
|---|
| 221 | ZERO(pMutex);
|
|---|
| 222 |
|
|---|
| 223 | if ( (!(arc = DosCreateEventSem(NULL,
|
|---|
| 224 | &pMutex->hevWriterDone,
|
|---|
| 225 | 0,
|
|---|
| 226 | FALSE)))
|
|---|
| 227 | && (!(arc = DosCreateEventSem(NULL,
|
|---|
| 228 | &pMutex->hevReadersDone,
|
|---|
| 229 | 0,
|
|---|
| 230 | FALSE)))
|
|---|
| 231 | )
|
|---|
| 232 | {
|
|---|
| 233 | treeInit(&pMutex->ReaderThreadsTree, NULL);
|
|---|
| 234 | }
|
|---|
| 235 | }
|
|---|
| 236 |
|
|---|
| 237 | if (arc)
|
|---|
| 238 | semDeleteRWMutex((PHRW)&pMutex);
|
|---|
| 239 | else
|
|---|
| 240 | *phrw = (HRW)pMutex;
|
|---|
| 241 |
|
|---|
| 242 | UnlockGlobal();
|
|---|
| 243 | }
|
|---|
| 244 |
|
|---|
| 245 | return (arc);
|
|---|
| 246 | }
|
|---|
| 247 |
|
|---|
| 248 | /*
|
|---|
| 249 | *@@ semDeleteRWMutex:
|
|---|
| 250 | * deletes a RW mutex previously created by
|
|---|
| 251 | * semCreateRWMutex.
|
|---|
| 252 | *
|
|---|
| 253 | * Returns:
|
|---|
| 254 | *
|
|---|
| 255 | * -- NO_ERROR: sem was deleted, and *phrw
|
|---|
| 256 | * was set to NULLHANDLE.
|
|---|
| 257 | *
|
|---|
| 258 | * -- ERROR_SEM_BUSY: semaphore is currently
|
|---|
| 259 | * requested.
|
|---|
| 260 | */
|
|---|
| 261 |
|
|---|
| 262 | APIRET semDeleteRWMutex(PHRW phrw) // in/out: rwsem handle
|
|---|
| 263 | {
|
|---|
| 264 | APIRET arc = NO_ERROR;
|
|---|
| 265 |
|
|---|
| 266 | if (!(arc = LockGlobal()))
|
|---|
| 267 | {
|
|---|
| 268 | PRWMUTEX pMutex;
|
|---|
| 269 | if ( (phrw)
|
|---|
| 270 | && (pMutex = (PRWMUTEX)(*phrw))
|
|---|
| 271 | )
|
|---|
| 272 | {
|
|---|
| 273 | if ( (pMutex->cReaders)
|
|---|
| 274 | || (pMutex->cWriters)
|
|---|
| 275 | )
|
|---|
| 276 | arc = ERROR_SEM_BUSY;
|
|---|
| 277 | else
|
|---|
| 278 | {
|
|---|
| 279 | if ( (!(arc = DosCloseEventSem(pMutex->hevWriterDone)))
|
|---|
| 280 | && (!(arc = DosCloseEventSem(pMutex->hevReadersDone)))
|
|---|
| 281 | )
|
|---|
| 282 | {
|
|---|
| 283 | LONG cItems = pMutex->cReaderThreads;
|
|---|
| 284 | TREE **papNodes = treeBuildArray(pMutex->ReaderThreadsTree,
|
|---|
| 285 | &cItems);
|
|---|
| 286 | if (papNodes)
|
|---|
| 287 | {
|
|---|
| 288 | ULONG ul;
|
|---|
| 289 | for (ul = 0; ul < cItems; ul++)
|
|---|
| 290 | free(papNodes[ul]);
|
|---|
| 291 |
|
|---|
| 292 | free(papNodes);
|
|---|
| 293 | }
|
|---|
| 294 |
|
|---|
| 295 | free(pMutex);
|
|---|
| 296 | *phrw = NULLHANDLE;
|
|---|
| 297 | }
|
|---|
| 298 | }
|
|---|
| 299 | }
|
|---|
| 300 | else
|
|---|
| 301 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 302 |
|
|---|
| 303 | UnlockGlobal();
|
|---|
| 304 | }
|
|---|
| 305 |
|
|---|
| 306 | return (arc);
|
|---|
| 307 | }
|
|---|
| 308 |
|
|---|
| 309 | /*
|
|---|
| 310 | *@@ semRequestRead:
|
|---|
| 311 | * requests read access from the read-write mutex.
|
|---|
| 312 | *
|
|---|
| 313 | * Returns:
|
|---|
| 314 | *
|
|---|
| 315 | * -- NO_ERROR: caller has read access and must
|
|---|
| 316 | * call semReleaseRead when done.
|
|---|
| 317 | *
|
|---|
| 318 | * -- ERROR_INVALID_PARAMETER
|
|---|
| 319 | *
|
|---|
| 320 | * -- ERROR_TIMEOUT
|
|---|
| 321 | *
|
|---|
| 322 | * This function will block only if another thread
|
|---|
| 323 | * currently holds write access.
|
|---|
| 324 | *
|
|---|
| 325 | * It will not block if other threads also have
|
|---|
| 326 | * write access, or it is the current thread which
|
|---|
| 327 | * holds write access, or if this is a nested read
|
|---|
| 328 | * request on the same thread.
|
|---|
| 329 | *
|
|---|
| 330 | * If this function returns NO_ERROR, the calling
|
|---|
| 331 | * thread is stored as a reader in the read-write
|
|---|
| 332 | * mutex and will block out other threads which
|
|---|
| 333 | * call semRequestWrite.
|
|---|
| 334 | */
|
|---|
| 335 |
|
|---|
| 336 | APIRET semRequestRead(HRW hrw, // in: rw-sem created by semCreateRWMutex
|
|---|
| 337 | ULONG ulTimeout) // in: timeout in ms, or SEM_INDEFINITE_WAIT
|
|---|
| 338 | {
|
|---|
| 339 | APIRET arc = NO_ERROR;
|
|---|
| 340 | BOOL fLocked = FALSE;
|
|---|
| 341 |
|
|---|
| 342 | // protect the RW by requesting the global mutex;
|
|---|
| 343 | // note, we ignore ulTimeout here, since this request
|
|---|
| 344 | // will only ever take a very short time
|
|---|
| 345 | if (!(arc = LockGlobal()))
|
|---|
| 346 | {
|
|---|
| 347 | PRWMUTEX pMutex = (PRWMUTEX)hrw;
|
|---|
| 348 | fLocked = TRUE;
|
|---|
| 349 |
|
|---|
| 350 | if (!pMutex)
|
|---|
| 351 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 352 | else
|
|---|
| 353 | {
|
|---|
| 354 | // get our own thread ID
|
|---|
| 355 | ULONG tidMyself = doshMyTID();
|
|---|
| 356 |
|
|---|
| 357 | // if there are any writers in the RW
|
|---|
| 358 | // besides our own thread, wait for the
|
|---|
| 359 | // writer to release write
|
|---|
| 360 | if ( (pMutex->cWriters)
|
|---|
| 361 | && (pMutex->tidWriter != tidMyself)
|
|---|
| 362 | )
|
|---|
| 363 | {
|
|---|
| 364 | while ( (pMutex->cWriters)
|
|---|
| 365 | && (!arc)
|
|---|
| 366 | )
|
|---|
| 367 | {
|
|---|
| 368 | ULONG ul;
|
|---|
| 369 | DosResetEventSem(pMutex->hevWriterDone, &ul);
|
|---|
| 370 |
|
|---|
| 371 | // while we're waiting on the writer to post
|
|---|
| 372 | // "writers done", release the global mutex
|
|---|
| 373 | UnlockGlobal();
|
|---|
| 374 | fLocked = FALSE;
|
|---|
| 375 |
|
|---|
| 376 | // block on "writer done"; this gets posted from
|
|---|
| 377 | // semReleaseWrite after the writer has released
|
|---|
| 378 | // its last write request...
|
|---|
| 379 | // so after this unblocks, we must check cWriters
|
|---|
| 380 | // again, in case another writer has come in between
|
|---|
| 381 | if (!(arc = WinWaitEventSem(pMutex->hevWriterDone, ulTimeout)))
|
|---|
| 382 | // writer done:
|
|---|
| 383 | // request global mutex again
|
|---|
| 384 | if (!(arc = LockGlobal()))
|
|---|
| 385 | fLocked = TRUE;
|
|---|
| 386 | // else: probably timeout, do not loop again
|
|---|
| 387 | }
|
|---|
| 388 | }
|
|---|
| 389 |
|
|---|
| 390 | if (!arc)
|
|---|
| 391 | {
|
|---|
| 392 | PREADERTREENODE pReader;
|
|---|
| 393 |
|
|---|
| 394 | // add readers count
|
|---|
| 395 | (pMutex->cReaders)++;
|
|---|
| 396 |
|
|---|
| 397 | // check if this thread has a reader entry already
|
|---|
| 398 | if (pReader = (PREADERTREENODE)treeFind(pMutex->ReaderThreadsTree,
|
|---|
| 399 | tidMyself, // ID to look for
|
|---|
| 400 | treeCompareKeys))
|
|---|
| 401 | {
|
|---|
| 402 | // yes:
|
|---|
| 403 | // this is either
|
|---|
| 404 | // -- a nested read request for the same thread
|
|---|
| 405 | // -- or a tree item from a previous read request
|
|---|
| 406 | // which went to 0, but wasn't deleted for speed
|
|---|
| 407 | // (cRequests is then 0)
|
|---|
| 408 | (pReader->cRequests)++;
|
|---|
| 409 | }
|
|---|
| 410 | else
|
|---|
| 411 | {
|
|---|
| 412 | // no entry for this thread yet:
|
|---|
| 413 | // add a new one
|
|---|
| 414 | pReader = NEW(READERTREENODE);
|
|---|
| 415 | if (!pReader)
|
|---|
| 416 | arc = ERROR_NOT_ENOUGH_MEMORY;
|
|---|
| 417 | else
|
|---|
| 418 | {
|
|---|
| 419 | // store the thread ID as the tree ID to
|
|---|
| 420 | // sort by (so we can find by TID)
|
|---|
| 421 | pReader->Tree.ulKey = tidMyself;
|
|---|
| 422 | // set requests count to 1
|
|---|
| 423 | pReader->cRequests = 1;
|
|---|
| 424 |
|
|---|
| 425 | treeInsert(&pMutex->ReaderThreadsTree,
|
|---|
| 426 | NULL,
|
|---|
| 427 | (TREE*)pReader,
|
|---|
| 428 | treeCompareKeys);
|
|---|
| 429 | (pMutex->cReaderThreads)++;
|
|---|
| 430 | }
|
|---|
| 431 | }
|
|---|
| 432 | }
|
|---|
| 433 | }
|
|---|
| 434 | } // end if (!(arc = LockGlobal()))
|
|---|
| 435 |
|
|---|
| 436 | if (fLocked)
|
|---|
| 437 | UnlockGlobal();
|
|---|
| 438 |
|
|---|
| 439 | return (arc);
|
|---|
| 440 | }
|
|---|
| 441 |
|
|---|
| 442 | /*
|
|---|
| 443 | *@@ semReleaseRead:
|
|---|
| 444 | * releases read access previously requested
|
|---|
| 445 | * by semRequestRead.
|
|---|
| 446 | *
|
|---|
| 447 | * This may unblock other threads which have
|
|---|
| 448 | * blocked in semRequestWrite.
|
|---|
| 449 | */
|
|---|
| 450 |
|
|---|
| 451 | APIRET semReleaseRead(HRW hrw) // in: rw-sem created by semCreateRWMutex
|
|---|
| 452 | {
|
|---|
| 453 | APIRET arc = NO_ERROR;
|
|---|
| 454 |
|
|---|
| 455 | // protect the RW by requesting global mutex
|
|---|
| 456 | if (!(arc = LockGlobal()))
|
|---|
| 457 | {
|
|---|
| 458 | PRWMUTEX pMutex = (PRWMUTEX)hrw;
|
|---|
| 459 |
|
|---|
| 460 | if (!pMutex)
|
|---|
| 461 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 462 | else
|
|---|
| 463 | {
|
|---|
| 464 | // get our own thread ID
|
|---|
| 465 | ULONG tidMyself = doshMyTID();
|
|---|
| 466 |
|
|---|
| 467 | PREADERTREENODE pReader;
|
|---|
| 468 |
|
|---|
| 469 | // find the READERTREENODE for our TID
|
|---|
| 470 | if ( (pReader = (PREADERTREENODE)treeFind(pMutex->ReaderThreadsTree,
|
|---|
| 471 | tidMyself, // ID to look for
|
|---|
| 472 | treeCompareKeys))
|
|---|
| 473 | && (pReader->cRequests)
|
|---|
| 474 | )
|
|---|
| 475 | {
|
|---|
| 476 | // lower user count then (will be zero now,
|
|---|
| 477 | // unless read requests were nested)
|
|---|
| 478 | (pReader->cRequests)--;
|
|---|
| 479 |
|
|---|
| 480 | // note: we don't delete the tree item,
|
|---|
| 481 | // since it will probably be reused soon
|
|---|
| 482 | // (speed)
|
|---|
| 483 |
|
|---|
| 484 | // lower total requests count
|
|---|
| 485 | (pMutex->cReaders)--;
|
|---|
| 486 |
|
|---|
| 487 | if (pMutex->cReaders == 0)
|
|---|
| 488 | // no more readers now:
|
|---|
| 489 | // post "readers done" semaphore
|
|---|
| 490 | DosPostEventSem(pMutex->hevReadersDone);
|
|---|
| 491 | // this sets all threads blocking
|
|---|
| 492 | // in semRequestWrite to "ready"
|
|---|
| 493 | }
|
|---|
| 494 | else
|
|---|
| 495 | // excessive releases for this thread,
|
|---|
| 496 | // or this wasn't requested at all:
|
|---|
| 497 | arc = ERROR_NOT_OWNER;
|
|---|
| 498 | }
|
|---|
| 499 |
|
|---|
| 500 | UnlockGlobal();
|
|---|
| 501 |
|
|---|
| 502 | } // end if (!(arc = LockGlobal()))
|
|---|
| 503 |
|
|---|
| 504 | return (arc);
|
|---|
| 505 | }
|
|---|
| 506 |
|
|---|
| 507 | /*
|
|---|
| 508 | *@@ semQueryRead:
|
|---|
| 509 | * checks if the thread currently has read
|
|---|
| 510 | * access to the read-write semaphore.
|
|---|
| 511 | *
|
|---|
| 512 | * Returns:
|
|---|
| 513 | *
|
|---|
| 514 | * -- NO_ERROR if the same thread request
|
|---|
| 515 | * read access before and thus is a reader.
|
|---|
| 516 | *
|
|---|
| 517 | * -- ERROR_NOT_OWNER if the thread does not
|
|---|
| 518 | * currently have read access.
|
|---|
| 519 | */
|
|---|
| 520 |
|
|---|
| 521 | APIRET semQueryRead(HRW hrw) // in: rw-sem created by semCreateRWMutex
|
|---|
| 522 | {
|
|---|
| 523 | APIRET arc = NO_ERROR;
|
|---|
| 524 |
|
|---|
| 525 | // protect the RW by requesting global mutex
|
|---|
| 526 | if (!(arc = LockGlobal()))
|
|---|
| 527 | {
|
|---|
| 528 | PRWMUTEX pMutex = (PRWMUTEX)hrw;
|
|---|
| 529 |
|
|---|
| 530 | if (!pMutex)
|
|---|
| 531 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 532 | else
|
|---|
| 533 | {
|
|---|
| 534 | // get our own thread ID
|
|---|
| 535 | ULONG tidMyself = doshMyTID();
|
|---|
| 536 |
|
|---|
| 537 | PREADERTREENODE pReader;
|
|---|
| 538 |
|
|---|
| 539 | // find the READERTREENODE for our TID
|
|---|
| 540 | if ( (!(pReader = (PREADERTREENODE)treeFind(pMutex->ReaderThreadsTree,
|
|---|
| 541 | tidMyself, // ID to look for
|
|---|
| 542 | treeCompareKeys)))
|
|---|
| 543 | || (pReader->cRequests == 0)
|
|---|
| 544 | )
|
|---|
| 545 | arc = ERROR_NOT_OWNER;
|
|---|
| 546 | // else: pReader exists, and pReader->cRequests > 0 --> NO_ERROR
|
|---|
| 547 | }
|
|---|
| 548 |
|
|---|
| 549 | UnlockGlobal();
|
|---|
| 550 |
|
|---|
| 551 | } // end if (!(arc = LockGlobal()))
|
|---|
| 552 |
|
|---|
| 553 | return (arc);
|
|---|
| 554 | }
|
|---|
| 555 |
|
|---|
| 556 | /*
|
|---|
| 557 | *@@ semRequestWrite:
|
|---|
| 558 | * requests write access from the read-write mutex.
|
|---|
| 559 | *
|
|---|
| 560 | * Returns:
|
|---|
| 561 | *
|
|---|
| 562 | * -- NO_ERROR: caller has write access and must
|
|---|
| 563 | * call semReleaseWrite when done.
|
|---|
| 564 | *
|
|---|
| 565 | * -- ERROR_INVALID_PARAMETER
|
|---|
| 566 | *
|
|---|
| 567 | * -- ERROR_TIMEOUT
|
|---|
| 568 | *
|
|---|
| 569 | * This function will block if any other thread
|
|---|
| 570 | * currently has read or write access.
|
|---|
| 571 | *
|
|---|
| 572 | * It will not block if the current thread is the
|
|---|
| 573 | * only thread that has requested read access
|
|---|
| 574 | * before, or if this is a nested write request
|
|---|
| 575 | * on the same thread.
|
|---|
| 576 | *
|
|---|
| 577 | * If this function returns NO_ERROR, the calling
|
|---|
| 578 | * thread owns the read-write mutex all alone,
|
|---|
| 579 | * as if it were a regular mutex. While write
|
|---|
| 580 | * access is held, other threads are blocked in
|
|---|
| 581 | * semRequestRead or semRequestWrite.
|
|---|
| 582 | */
|
|---|
| 583 |
|
|---|
| 584 | APIRET semRequestWrite(HRW hrw, // in: rw-sem created by semCreateRWMutex
|
|---|
| 585 | ULONG ulTimeout) // in: timeout in ms, or SEM_INDEFINITE_WAIT
|
|---|
| 586 | {
|
|---|
| 587 | APIRET arc = NO_ERROR;
|
|---|
| 588 | BOOL fLocked = FALSE;
|
|---|
| 589 |
|
|---|
| 590 | // protect the RW by requesting global mutex;
|
|---|
| 591 | // note, we ignore ulTimeout here, since this request
|
|---|
| 592 | // will only ever take a very short time
|
|---|
| 593 | if (!(arc = LockGlobal()))
|
|---|
| 594 | {
|
|---|
| 595 | PRWMUTEX pMutex = (PRWMUTEX)hrw;
|
|---|
| 596 | fLocked = TRUE;
|
|---|
| 597 |
|
|---|
| 598 | if (!pMutex)
|
|---|
| 599 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 600 | else
|
|---|
| 601 | {
|
|---|
| 602 | // get our own thread ID
|
|---|
| 603 | ULONG tidMyself = doshMyTID();
|
|---|
| 604 |
|
|---|
| 605 | while (!arc)
|
|---|
| 606 | {
|
|---|
| 607 | // check if current TID holds read request also
|
|---|
| 608 | PREADERTREENODE pReader
|
|---|
| 609 | = (PREADERTREENODE)treeFind(pMutex->ReaderThreadsTree,
|
|---|
| 610 | tidMyself,
|
|---|
| 611 | treeCompareKeys);
|
|---|
| 612 | // != NULL if this TID has a reader already
|
|---|
| 613 |
|
|---|
| 614 | // let the writer in if one of the three is true:
|
|---|
| 615 | if (
|
|---|
| 616 | // 1) no readers and no writers at all currently
|
|---|
| 617 | ( (pMutex->cWriters == 0)
|
|---|
| 618 | && (pMutex->cReaders == 0)
|
|---|
| 619 | )
|
|---|
| 620 | // or 2) there is a writer (which implies that there
|
|---|
| 621 | // are no readers), but the writer has the
|
|---|
| 622 | // same TID as the caller --> nested writer call
|
|---|
| 623 | // on the same thread
|
|---|
| 624 | || ( (pMutex->cWriters)
|
|---|
| 625 | && (pMutex->tidWriter == tidMyself)
|
|---|
| 626 | )
|
|---|
| 627 | // or 3) a reader tree item was found above, and
|
|---|
| 628 | // current thread is the only reader
|
|---|
| 629 | || ( (pReader)
|
|---|
| 630 | && (pReader->cRequests)
|
|---|
| 631 | && (pReader->cRequests == pMutex->cReaders)
|
|---|
| 632 | )
|
|---|
| 633 | )
|
|---|
| 634 | {
|
|---|
| 635 | // we're safe!
|
|---|
| 636 | break;
|
|---|
| 637 | }
|
|---|
| 638 | else
|
|---|
| 639 | {
|
|---|
| 640 | // we're NOT safe:
|
|---|
| 641 | // this means that
|
|---|
| 642 | // 1) a writer other than current thread is active, or
|
|---|
| 643 | // 2) readers exist and we're not the only reader...
|
|---|
| 644 | // block then until "readers done" is posted
|
|---|
| 645 | ULONG ul;
|
|---|
| 646 | DosResetEventSem(pMutex->hevReadersDone, &ul);
|
|---|
| 647 |
|
|---|
| 648 | // while we're waiting on the last reader to post
|
|---|
| 649 | // "readers done", release the global mutex
|
|---|
| 650 | UnlockGlobal();
|
|---|
| 651 | fLocked = FALSE;
|
|---|
| 652 |
|
|---|
| 653 | // wait for all readers and writers to finish;
|
|---|
| 654 | // this gets posted by
|
|---|
| 655 | // -- semReleaseRead if pMutex->cReaders goes to 0
|
|---|
| 656 | // -- semReleaseWrite after another writer has
|
|---|
| 657 | // released its last write request
|
|---|
| 658 | if (!(arc = WinWaitEventSem(pMutex->hevReadersDone, ulTimeout)))
|
|---|
| 659 | // readers done:
|
|---|
| 660 | // request global mutex again
|
|---|
| 661 | if (!(arc = LockGlobal()))
|
|---|
| 662 | fLocked = TRUE;
|
|---|
| 663 | // else: probably timeout, do not loop again
|
|---|
| 664 | }
|
|---|
| 665 | } // end while (!arc)
|
|---|
| 666 |
|
|---|
| 667 | if (!arc)
|
|---|
| 668 | {
|
|---|
| 669 | // OK, raise writers count
|
|---|
| 670 | (pMutex->cWriters)++;
|
|---|
| 671 | // and store our TID as the current writer
|
|---|
| 672 | pMutex->tidWriter = tidMyself;
|
|---|
| 673 | }
|
|---|
| 674 | }
|
|---|
| 675 |
|
|---|
| 676 | if (fLocked)
|
|---|
| 677 | UnlockGlobal();
|
|---|
| 678 |
|
|---|
| 679 | } // end if (!(arc = LockGlobal()))
|
|---|
| 680 |
|
|---|
| 681 | return (arc);
|
|---|
| 682 | }
|
|---|
| 683 |
|
|---|
| 684 | /*
|
|---|
| 685 | *@@ semReleaseWrite:
|
|---|
| 686 | * releases write access previously requested
|
|---|
| 687 | * by semRequestWrite.
|
|---|
| 688 | *
|
|---|
| 689 | * This may unblock other threads which have
|
|---|
| 690 | * blocked in either semRequestRead or
|
|---|
| 691 | * semRequestWrite.
|
|---|
| 692 | */
|
|---|
| 693 |
|
|---|
| 694 | APIRET semReleaseWrite(HRW hrw) // in: rw-sem created by semCreateRWMutex
|
|---|
| 695 | {
|
|---|
| 696 | APIRET arc = NO_ERROR;
|
|---|
| 697 |
|
|---|
| 698 | // protect the RW by requesting the global mutex
|
|---|
| 699 | if (!(arc = LockGlobal()))
|
|---|
| 700 | {
|
|---|
| 701 | PRWMUTEX pMutex = (PRWMUTEX)hrw;
|
|---|
| 702 |
|
|---|
| 703 | if (!pMutex)
|
|---|
| 704 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 705 | else
|
|---|
| 706 | {
|
|---|
| 707 | // get our own thread ID
|
|---|
| 708 | ULONG tidMyself = doshMyTID();
|
|---|
| 709 |
|
|---|
| 710 | if ( (pMutex->cWriters)
|
|---|
| 711 | && (pMutex->tidWriter == tidMyself)
|
|---|
| 712 | )
|
|---|
| 713 | {
|
|---|
| 714 | (pMutex->cWriters)--;
|
|---|
| 715 |
|
|---|
| 716 | if (pMutex->cWriters == 0)
|
|---|
| 717 | {
|
|---|
| 718 | ULONG ul;
|
|---|
| 719 | // last write request released:
|
|---|
| 720 | // post the "writer done" semaphore
|
|---|
| 721 | DosResetEventSem(pMutex->hevWriterDone, &ul);
|
|---|
| 722 | DosPostEventSem(pMutex->hevWriterDone);
|
|---|
| 723 | // this sets all threads blocking
|
|---|
| 724 | // in semRequestRead to "ready"
|
|---|
| 725 |
|
|---|
| 726 | // and post the "reader done" semaphore
|
|---|
| 727 | // as well, in case another thread is
|
|---|
| 728 | // waiting for write request
|
|---|
| 729 | DosResetEventSem(pMutex->hevReadersDone, &ul);
|
|---|
| 730 | DosPostEventSem(pMutex->hevReadersDone);
|
|---|
| 731 | // this sets all threads blocking
|
|---|
| 732 | // in semRequestWrite to "ready"
|
|---|
| 733 |
|
|---|
| 734 | // and set tidWriter to 0
|
|---|
| 735 | pMutex->tidWriter = 0;
|
|---|
| 736 | }
|
|---|
| 737 | // else: nested write request on this
|
|---|
| 738 | // thread (there can only ever be one
|
|---|
| 739 | // writer thread)
|
|---|
| 740 | }
|
|---|
| 741 | else
|
|---|
| 742 | // excessive releases for this thread,
|
|---|
| 743 | // or this wasn't requested at all:
|
|---|
| 744 | arc = ERROR_NOT_OWNER;
|
|---|
| 745 | }
|
|---|
| 746 |
|
|---|
| 747 | UnlockGlobal();
|
|---|
| 748 |
|
|---|
| 749 | } // end if (!(arc = LockGlobal()))
|
|---|
| 750 |
|
|---|
| 751 | return (arc);
|
|---|
| 752 | }
|
|---|
| 753 |
|
|---|
| 754 | /*
|
|---|
| 755 | *@@ semQueryWrite:
|
|---|
| 756 | * checks if the thread currently has write
|
|---|
| 757 | * access to the read-write semaphore.
|
|---|
| 758 | *
|
|---|
| 759 | * Returns:
|
|---|
| 760 | *
|
|---|
| 761 | * -- NO_ERROR if the same thread request
|
|---|
| 762 | * write access before and thus is a writer.
|
|---|
| 763 | *
|
|---|
| 764 | * -- ERROR_NOT_OWNER if the thread does not
|
|---|
| 765 | * currently have write access.
|
|---|
| 766 | */
|
|---|
| 767 |
|
|---|
| 768 | APIRET semQueryWrite(HRW hrw) // in: rw-sem created by semCreateRWMutex
|
|---|
| 769 | {
|
|---|
| 770 | APIRET arc = NO_ERROR;
|
|---|
| 771 |
|
|---|
| 772 | // protect the RW by requesting the global mutex
|
|---|
| 773 | if (!(arc = LockGlobal()))
|
|---|
| 774 | {
|
|---|
| 775 | PRWMUTEX pMutex = (PRWMUTEX)hrw;
|
|---|
| 776 |
|
|---|
| 777 | if (!pMutex)
|
|---|
| 778 | arc = ERROR_INVALID_PARAMETER;
|
|---|
| 779 | else
|
|---|
| 780 | {
|
|---|
| 781 | // get our own thread ID
|
|---|
| 782 | ULONG tidMyself = doshMyTID();
|
|---|
| 783 |
|
|---|
| 784 | if ( (pMutex->cWriters == 0)
|
|---|
| 785 | || (pMutex->tidWriter != tidMyself)
|
|---|
| 786 | )
|
|---|
| 787 | arc = ERROR_NOT_OWNER;
|
|---|
| 788 | }
|
|---|
| 789 |
|
|---|
| 790 | UnlockGlobal();
|
|---|
| 791 |
|
|---|
| 792 | } // end if (!(arc = LockGlobal()))
|
|---|
| 793 |
|
|---|
| 794 | return (arc);
|
|---|
| 795 | }
|
|---|
| 796 |
|
|---|
| 797 |
|
|---|