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

Last change on this file since 94 was 83, checked in by umoeller, 24 years ago

Many misc updates.

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