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

Last change on this file since 165 was 142, checked in by umoeller, 24 years ago

misc. updates

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 24.8 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
173static 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
190static 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
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, 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
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 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
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 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
451APIRET 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
521APIRET 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
584APIRET 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
694APIRET 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
768APIRET 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
Note: See TracBrowser for help on using the repository browser.