source: trunk/src/helpers/semaphores.c@ 80

Last change on this file since 80 was 78, checked in by umoeller, 24 years ago

Misc changes.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 24.5 KB
Line 
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
93typedef 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
139typedef 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
157static 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
173APIRET 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
190VOID 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
210APIRET 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);
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
262APIRET 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 ULONG 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
336APIRET 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)treeFindEQID(&pMutex->ReaderThreadsTree,
399 tidMyself)) // ID to look for
400 {
401 // yes:
402 // this is either
403 // -- a nested read request for the same thread
404 // -- or a tree item from a previous read request
405 // which went to 0, but wasn't deleted for speed
406 // (cRequests is then 0)
407 (pReader->cRequests)++;
408 }
409 else
410 {
411 // no entry for this thread yet:
412 // add a new one
413 pReader = NEW(READERTREENODE);
414 if (!pReader)
415 arc = ERROR_NOT_ENOUGH_MEMORY;
416 else
417 {
418 // store the thread ID as the tree ID to
419 // sort by (so we can find by TID)
420 pReader->Tree.id = tidMyself;
421 // set requests count to 1
422 pReader->cRequests = 1;
423
424 treeInsertID(&pMutex->ReaderThreadsTree,
425 (TREE*)pReader,
426 FALSE);
427 (pMutex->cReaderThreads)++;
428 }
429 }
430 }
431 }
432 } // end if (!(arc = LockGlobal()))
433
434 if (fLocked)
435 UnlockGlobal();
436
437 return (arc);
438}
439
440/*
441 *@@ semReleaseRead:
442 * releases read access previously requested
443 * by semRequestRead.
444 *
445 * This may unblock other threads which have
446 * blocked in semRequestWrite.
447 */
448
449APIRET semReleaseRead(HRW hrw) // in: rw-sem created by semCreateRWMutex
450{
451 APIRET arc = NO_ERROR;
452
453 // protect the RW by requesting global mutex
454 if (!(arc = LockGlobal()))
455 {
456 PRWMUTEX pMutex = (PRWMUTEX)hrw;
457
458 if (!pMutex)
459 arc = ERROR_INVALID_PARAMETER;
460 else
461 {
462 // get our own thread ID
463 ULONG tidMyself = doshMyTID();
464
465 PREADERTREENODE pReader;
466
467 // find the READERTREENODE for our TID
468 if ( (pReader = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
469 tidMyself)) // ID to look for
470 && (pReader->cRequests)
471 )
472 {
473 // lower user count then (will be zero now,
474 // unless read requests were nested)
475 (pReader->cRequests)--;
476
477 // note: we don't delete the tree item,
478 // since it will probably be reused soon
479 // (speed)
480
481 // lower total requests count
482 (pMutex->cReaders)--;
483
484 if (pMutex->cReaders == 0)
485 // no more readers now:
486 // post "readers done" semaphore
487 DosPostEventSem(pMutex->hevReadersDone);
488 // this sets all threads blocking
489 // in semRequestWrite to "ready"
490 }
491 else
492 // excessive releases for this thread,
493 // or this wasn't requested at all:
494 arc = ERROR_NOT_OWNER;
495 }
496
497 UnlockGlobal();
498
499 } // end if (!(arc = LockGlobal()))
500
501 return (arc);
502}
503
504/*
505 *@@ semQueryRead:
506 * checks if the thread currently has read
507 * access to the read-write semaphore.
508 *
509 * Returns:
510 *
511 * -- NO_ERROR if the same thread request
512 * read access before and thus is a reader.
513 *
514 * -- ERROR_NOT_OWNER if the thread does not
515 * currently have read access.
516 */
517
518APIRET semQueryRead(HRW hrw) // in: rw-sem created by semCreateRWMutex
519{
520 APIRET arc = NO_ERROR;
521
522 // protect the RW by requesting global mutex
523 if (!(arc = LockGlobal()))
524 {
525 PRWMUTEX pMutex = (PRWMUTEX)hrw;
526
527 if (!pMutex)
528 arc = ERROR_INVALID_PARAMETER;
529 else
530 {
531 // get our own thread ID
532 ULONG tidMyself = doshMyTID();
533
534 PREADERTREENODE pReader;
535
536 // find the READERTREENODE for our TID
537 if ( (!(pReader = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
538 tidMyself))) // ID to look for
539 || (pReader->cRequests == 0)
540 )
541 arc = ERROR_NOT_OWNER;
542 // else: pReader exists, and pReader->cRequests > 0 --> NO_ERROR
543 }
544
545 UnlockGlobal();
546
547 } // end if (!(arc = LockGlobal()))
548
549 return (arc);
550}
551
552/*
553 *@@ semRequestWrite:
554 * requests write access from the read-write mutex.
555 *
556 * Returns:
557 *
558 * -- NO_ERROR: caller has write access and must
559 * call semReleaseWrite when done.
560 *
561 * -- ERROR_INVALID_PARAMETER
562 *
563 * -- ERROR_TIMEOUT
564 *
565 * This function will block if any other thread
566 * currently has read or write access.
567 *
568 * It will not block if the current thread is the
569 * only thread that has requested read access
570 * before, or if this is a nested write request
571 * on the same thread.
572 *
573 * If this function returns NO_ERROR, the calling
574 * thread owns the read-write mutex all alone,
575 * as if it were a regular mutex. While write
576 * access is held, other threads are blocked in
577 * semRequestRead or semRequestWrite.
578 */
579
580APIRET semRequestWrite(HRW hrw, // in: rw-sem created by semCreateRWMutex
581 ULONG ulTimeout) // in: timeout in ms, or SEM_INDEFINITE_WAIT
582{
583 APIRET arc = NO_ERROR;
584 BOOL fLocked = FALSE;
585
586 // protect the RW by requesting global mutex;
587 // note, we ignore ulTimeout here, since this request
588 // will only ever take a very short time
589 if (!(arc = LockGlobal()))
590 {
591 PRWMUTEX pMutex = (PRWMUTEX)hrw;
592 fLocked = TRUE;
593
594 if (!pMutex)
595 arc = ERROR_INVALID_PARAMETER;
596 else
597 {
598 // get our own thread ID
599 ULONG tidMyself = doshMyTID();
600
601 while (!arc)
602 {
603 // check if current TID holds read request also
604 PREADERTREENODE pReader
605 = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
606 tidMyself);
607 // != NULL if this TID has a reader already
608
609 // let the writer in if one of the three is true:
610 if (
611 // 1) no readers and no writers at all currently
612 ( (pMutex->cWriters == 0)
613 && (pMutex->cReaders == 0)
614 )
615 // or 2) there is a writer (which implies that there
616 // are no readers), but the writer has the
617 // same TID as the caller --> nested writer call
618 // on the same thread
619 || ( (pMutex->cWriters)
620 && (pMutex->tidWriter == tidMyself)
621 )
622 // or 3) a reader tree item was found above, and
623 // current thread is the only reader
624 || ( (pReader)
625 && (pReader->cRequests)
626 && (pReader->cRequests == pMutex->cReaders)
627 )
628 )
629 {
630 // we're safe!
631 break;
632 }
633 else
634 {
635 // we're NOT safe:
636 // this means that
637 // 1) a writer other than current thread is active, or
638 // 2) readers exist and we're not the only reader...
639 // block then until "readers done" is posted
640 ULONG ul;
641 DosResetEventSem(pMutex->hevReadersDone, &ul);
642
643 // while we're waiting on the last reader to post
644 // "readers done", release the global mutex
645 UnlockGlobal();
646 fLocked = FALSE;
647
648 // wait for all readers and writers to finish;
649 // this gets posted by
650 // -- semReleaseRead if pMutex->cReaders goes to 0
651 // -- semReleaseWrite after another writer has
652 // released its last write request
653 if (!(arc = WinWaitEventSem(pMutex->hevReadersDone, ulTimeout)))
654 // readers done:
655 // request global mutex again
656 if (!(arc = LockGlobal()))
657 fLocked = TRUE;
658 // else: probably timeout, do not loop again
659 }
660 } // end while (!arc)
661
662 if (!arc)
663 {
664 // OK, raise writers count
665 (pMutex->cWriters)++;
666 // and store our TID as the current writer
667 pMutex->tidWriter = tidMyself;
668 }
669 }
670
671 if (fLocked)
672 UnlockGlobal();
673
674 } // end if (!(arc = LockGlobal()))
675
676 return (arc);
677}
678
679/*
680 *@@ semReleaseWrite:
681 * releases write access previously requested
682 * by semRequestWrite.
683 *
684 * This may unblock other threads which have
685 * blocked in either semRequestRead or
686 * semRequestWrite.
687 */
688
689APIRET semReleaseWrite(HRW hrw) // in: rw-sem created by semCreateRWMutex
690{
691 APIRET arc = NO_ERROR;
692
693 // protect the RW by requesting the global mutex
694 if (!(arc = LockGlobal()))
695 {
696 PRWMUTEX pMutex = (PRWMUTEX)hrw;
697
698 if (!pMutex)
699 arc = ERROR_INVALID_PARAMETER;
700 else
701 {
702 // get our own thread ID
703 ULONG tidMyself = doshMyTID();
704
705 if ( (pMutex->cWriters)
706 && (pMutex->tidWriter == tidMyself)
707 )
708 {
709 (pMutex->cWriters)--;
710
711 if (pMutex->cWriters == 0)
712 {
713 ULONG ul;
714 // last write request released:
715 // post the "writer done" semaphore
716 DosResetEventSem(pMutex->hevWriterDone, &ul);
717 DosPostEventSem(pMutex->hevWriterDone);
718 // this sets all threads blocking
719 // in semRequestRead to "ready"
720
721 // and post the "reader done" semaphore
722 // as well, in case another thread is
723 // waiting for write request
724 DosResetEventSem(pMutex->hevReadersDone, &ul);
725 DosPostEventSem(pMutex->hevReadersDone);
726 // this sets all threads blocking
727 // in semRequestWrite to "ready"
728
729 // and set tidWriter to 0
730 pMutex->tidWriter = 0;
731 }
732 // else: nested write request on this
733 // thread (there can only ever be one
734 // writer thread)
735 }
736 else
737 // excessive releases for this thread,
738 // or this wasn't requested at all:
739 arc = ERROR_NOT_OWNER;
740 }
741
742 UnlockGlobal();
743
744 } // end if (!(arc = LockGlobal()))
745
746 return (arc);
747}
748
749/*
750 *@@ semQueryWrite:
751 * checks if the thread currently has write
752 * access to the read-write semaphore.
753 *
754 * Returns:
755 *
756 * -- NO_ERROR if the same thread request
757 * write access before and thus is a writer.
758 *
759 * -- ERROR_NOT_OWNER if the thread does not
760 * currently have write access.
761 */
762
763APIRET semQueryWrite(HRW hrw) // in: rw-sem created by semCreateRWMutex
764{
765 APIRET arc = NO_ERROR;
766
767 // protect the RW by requesting the global mutex
768 if (!(arc = LockGlobal()))
769 {
770 PRWMUTEX pMutex = (PRWMUTEX)hrw;
771
772 if (!pMutex)
773 arc = ERROR_INVALID_PARAMETER;
774 else
775 {
776 // get our own thread ID
777 ULONG tidMyself = doshMyTID();
778
779 if ( (pMutex->cWriters == 0)
780 || (pMutex->tidWriter != tidMyself)
781 )
782 arc = ERROR_NOT_OWNER;
783 }
784
785 UnlockGlobal();
786
787 } // end if (!(arc = LockGlobal()))
788
789 return (arc);
790}
791
792
Note: See TracBrowser for help on using the repository browser.