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

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

Added apps.c, semaphores.c

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 24.4 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 *@@added V0.9.12 (2001-05-27) [umoeller]
172 */
173
174APIRET LockGlobal(VOID)
175{
176 if (!G_hmtxGlobal)
177 // first call:
178 return (DosCreateMutexSem(NULL,
179 &G_hmtxGlobal,
180 0,
181 TRUE)); // request!
182
183 return (WinRequestMutexSem(G_hmtxGlobal, SEM_INDEFINITE_WAIT));
184}
185
186/*
187 *@@ UnlockGlobal:
188 *
189 *@@added V0.9.12 (2001-05-27) [umoeller]
190 */
191
192VOID UnlockGlobal(VOID)
193{
194 DosReleaseMutexSem(G_hmtxGlobal);
195}
196
197/* ******************************************************************
198 *
199 * Public interfaces
200 *
201 ********************************************************************/
202
203/*
204 *@@ semCreateRWMutex:
205 * creates a read-write mutex.
206 *
207 * If this returns NO_ERROR, a new RWMUTEX
208 * has been created in *ppMutex. You must
209 * use semDeleteRWMutex to free it again.
210 */
211
212APIRET semCreateRWMutex(PHRW phrw)
213{
214 APIRET arc = NO_ERROR;
215
216 if (!(arc = LockGlobal()))
217 {
218 PRWMUTEX pMutex = NEW(RWMUTEX);
219 if (!pMutex)
220 arc = ERROR_NOT_ENOUGH_MEMORY;
221 else
222 {
223 ZERO(pMutex);
224
225 if ( (!(arc = DosCreateEventSem(NULL,
226 &pMutex->hevWriterDone,
227 0,
228 FALSE)))
229 && (!(arc = DosCreateEventSem(NULL,
230 &pMutex->hevReadersDone,
231 0,
232 FALSE)))
233 )
234 {
235 treeInit(&pMutex->ReaderThreadsTree);
236 }
237 }
238
239 if (arc)
240 semDeleteRWMutex((PHRW)&pMutex);
241 else
242 *phrw = (HRW)pMutex;
243
244 UnlockGlobal();
245 }
246
247 return (arc);
248}
249
250/*
251 *@@ semDeleteRWMutex:
252 * deletes a RW mutex previously created by
253 * semCreateRWMutex.
254 *
255 * Returns:
256 *
257 * -- NO_ERROR: sem was deleted, and *phrw
258 * was set to NULLHANDLE.
259 *
260 * -- ERROR_SEM_BUSY: couldn't delete one
261 * of the member semaphores because they
262 * were held by some other thread.
263 */
264
265APIRET semDeleteRWMutex(PHRW phrw)
266{
267 APIRET arc = NO_ERROR;
268
269 if (!(arc = LockGlobal()))
270 {
271 PRWMUTEX pMutex;
272 if ( (phrw)
273 && (pMutex = (PRWMUTEX)(*phrw))
274 )
275 {
276 if ( (!(arc = DosCloseEventSem(pMutex->hevWriterDone)))
277 && (!(arc = DosCloseEventSem(pMutex->hevReadersDone)))
278 )
279 {
280 ULONG cItems = pMutex->cReaderThreads;
281 TREE **papNodes = treeBuildArray(pMutex->ReaderThreadsTree,
282 &cItems);
283 if (papNodes)
284 {
285 ULONG ul;
286 for (ul = 0; ul < cItems; ul++)
287 free(papNodes[ul]);
288
289 free(papNodes);
290 }
291
292 free(pMutex);
293 *phrw = NULLHANDLE;
294 }
295 }
296 else
297 arc = ERROR_INVALID_PARAMETER;
298
299 UnlockGlobal();
300 }
301
302 return (arc);
303}
304
305/*
306 *@@ semRequestRead:
307 * requests read access from the read-write mutex.
308 *
309 * Returns:
310 *
311 * -- NO_ERROR: caller has read access and must
312 * call semReleaseRead when done.
313 *
314 * -- ERROR_INVALID_PARAMETER
315 *
316 * -- ERROR_TIMEOUT
317 *
318 * This function will block only if another thread
319 * currently holds write access.
320 *
321 * It will not block if other threads also have
322 * write access, or it is the current thread which
323 * holds write access, or if this is a nested read
324 * request on the same thread.
325 *
326 * If this function returns NO_ERROR, the calling
327 * thread is stored as a reader in the read-write
328 * mutex and will block out other threads which
329 * call semRequestWrite.
330 */
331
332APIRET semRequestRead(HRW hrw, // in: rw-sem created by semCreateRWMutex
333 ULONG ulTimeout) // in: timeout in ms, or SEM_INDEFINITE_WAIT
334{
335 APIRET arc = NO_ERROR;
336 BOOL fLocked = FALSE;
337
338 // protect the RW by requesting the global mutex;
339 // note, we ignore ulTimeout here, since this request
340 // will only ever take a very short time
341 if (!(arc = LockGlobal()))
342 {
343 PRWMUTEX pMutex = (PRWMUTEX)hrw;
344 fLocked = TRUE;
345
346 if (!pMutex)
347 arc = ERROR_INVALID_PARAMETER;
348 else
349 {
350 // get our own thread ID
351 ULONG tidMyself = doshMyTID();
352
353 // if there are any writers in the RW
354 // besides our own thread, wait for the
355 // writer to release write
356 if ( (pMutex->cWriters)
357 && (pMutex->tidWriter != tidMyself)
358 )
359 {
360 while ( (pMutex->cWriters)
361 && (!arc)
362 )
363 {
364 ULONG ul;
365 DosResetEventSem(pMutex->hevWriterDone, &ul);
366
367 // while we're waiting on the writer to post
368 // "writers done", release the global mutex
369 UnlockGlobal();
370 fLocked = FALSE;
371
372 // block on "writer done"; this gets posted from
373 // semReleaseWrite after the writer has released
374 // its last write request...
375 // so after this unblocks, we must check cWriters
376 // again, in case another writer has come in between
377 if (!(arc = WinWaitEventSem(pMutex->hevWriterDone, ulTimeout)))
378 // writer done:
379 // request global mutex again
380 if (!(arc = LockGlobal()))
381 fLocked = TRUE;
382 // else: probably timeout, do not loop again
383 }
384 }
385
386 if (!arc)
387 {
388 PREADERTREENODE pReader;
389
390 // add readers count
391 (pMutex->cReaders)++;
392
393 // check if this thread has a reader entry already
394 if (pReader = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
395 tidMyself)) // ID to look for
396 {
397 // yes:
398 // this is either
399 // -- a nested read request for the same thread
400 // -- or a tree item from a previous read request
401 // which went to 0, but wasn't deleted for speed
402 // (cRequests is then 0)
403 (pReader->cRequests)++;
404 }
405 else
406 {
407 // no entry for this thread yet:
408 // add a new one
409 pReader = NEW(READERTREENODE);
410 if (!pReader)
411 arc = ERROR_NOT_ENOUGH_MEMORY;
412 else
413 {
414 // store the thread ID as the tree ID to
415 // sort by (so we can find by TID)
416 pReader->Tree.id = tidMyself;
417 // set requests count to 1
418 pReader->cRequests = 1;
419
420 treeInsertID(&pMutex->ReaderThreadsTree,
421 (TREE*)pReader,
422 FALSE);
423 (pMutex->cReaderThreads)++;
424 }
425 }
426 }
427 }
428 } // end if (!(arc = LockGlobal()))
429
430 if (fLocked)
431 UnlockGlobal();
432
433 return (arc);
434}
435
436/*
437 *@@ semReleaseRead:
438 * releases read access previously requested
439 * by semRequestRead.
440 *
441 * This may unblock other threads which have
442 * blocked in semRequestWrite.
443 */
444
445APIRET semReleaseRead(HRW hrw) // in: rw-sem created by semCreateRWMutex
446{
447 APIRET arc = NO_ERROR;
448
449 // protect the RW by requesting global mutex
450 if (!(arc = LockGlobal()))
451 {
452 PRWMUTEX pMutex = (PRWMUTEX)hrw;
453
454 if (!pMutex)
455 arc = ERROR_INVALID_PARAMETER;
456 else
457 {
458 // get our own thread ID
459 ULONG tidMyself = doshMyTID();
460
461 PREADERTREENODE pReader;
462
463 // find the READERTREENODE for our TID
464 if ( (pReader = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
465 tidMyself)) // ID to look for
466 && (pReader->cRequests)
467 )
468 {
469 // lower user count then (will be zero now,
470 // unless read requests were nested)
471 (pReader->cRequests)--;
472
473 // note: we don't delete the tree item,
474 // since it will probably be reused soon
475 // (speed)
476
477 // lower total requests count
478 (pMutex->cReaders)--;
479
480 if (pMutex->cReaders == 0)
481 // no more readers now:
482 // post "readers done" semaphore
483 DosPostEventSem(pMutex->hevReadersDone);
484 // this sets all threads blocking
485 // in semRequestWrite to "ready"
486 }
487 else
488 // excessive releases for this thread,
489 // or this wasn't requested at all:
490 arc = ERROR_NOT_OWNER;
491 }
492
493 UnlockGlobal();
494
495 } // end if (!(arc = LockGlobal()))
496
497 return (arc);
498}
499
500/*
501 *@@ semQueryRead:
502 * checks if the thread currently has read
503 * access to the read-write semaphore.
504 *
505 * Returns:
506 *
507 * -- NO_ERROR if the same thread request
508 * read access before and thus is a reader.
509 *
510 * -- ERROR_NOT_OWNER if the thread does not
511 * currently have read access.
512 */
513
514APIRET semQueryRead(HRW hrw) // in: rw-sem created by semCreateRWMutex
515{
516 APIRET arc = NO_ERROR;
517
518 // protect the RW by requesting global mutex
519 if (!(arc = LockGlobal()))
520 {
521 PRWMUTEX pMutex = (PRWMUTEX)hrw;
522
523 if (!pMutex)
524 arc = ERROR_INVALID_PARAMETER;
525 else
526 {
527 // get our own thread ID
528 ULONG tidMyself = doshMyTID();
529
530 PREADERTREENODE pReader;
531
532 // find the READERTREENODE for our TID
533 if ( (!(pReader = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
534 tidMyself))) // ID to look for
535 || (pReader->cRequests == 0)
536 )
537 arc = ERROR_NOT_OWNER;
538 // else: pReader exists, and pReader->cRequests > 0 --> NO_ERROR
539 }
540
541 UnlockGlobal();
542
543 } // end if (!(arc = LockGlobal()))
544
545 return (arc);
546}
547
548/*
549 *@@ semRequestWrite:
550 * requests write access from the read-write mutex.
551 *
552 * Returns:
553 *
554 * -- NO_ERROR: caller has write access and must
555 * call semReleaseWrite when done.
556 *
557 * -- ERROR_INVALID_PARAMETER
558 *
559 * -- ERROR_TIMEOUT
560 *
561 * This function will block if any other thread
562 * currently has read or write access.
563 *
564 * It will not block if the current thread is the
565 * only thread that has requested read access
566 * before, or if this is a nested write request
567 * on the same thread.
568 *
569 * If this function returns NO_ERROR, the calling
570 * thread owns the read-write mutex all alone,
571 * as if it were a regular mutex. While write
572 * access is held, other threads are blocked in
573 * semRequestRead or semRequestWrite.
574 */
575
576APIRET semRequestWrite(HRW hrw, // in: rw-sem created by semCreateRWMutex
577 ULONG ulTimeout) // in: timeout in ms, or SEM_INDEFINITE_WAIT
578{
579 APIRET arc = NO_ERROR;
580 BOOL fLocked = FALSE;
581
582 // protect the RW by requesting global mutex;
583 // note, we ignore ulTimeout here, since this request
584 // will only ever take a very short time
585 if (!(arc = LockGlobal()))
586 {
587 PRWMUTEX pMutex = (PRWMUTEX)hrw;
588 fLocked = TRUE;
589
590 if (!pMutex)
591 arc = ERROR_INVALID_PARAMETER;
592 else
593 {
594 // get our own thread ID
595 ULONG tidMyself = doshMyTID();
596
597 while (!arc)
598 {
599 // check if current TID holds read request also
600 PREADERTREENODE pReader
601 = (PREADERTREENODE)treeFindEQID(&pMutex->ReaderThreadsTree,
602 tidMyself);
603 // != NULL if this TID has a reader already
604
605 // let the writer in if one of the three is true:
606 if (
607 // 1) no readers and no writers at all currently
608 ( (pMutex->cWriters == 0)
609 && (pMutex->cReaders == 0)
610 )
611 // or 2) there is a writer (which implies that there
612 // are no readers), but the writer has the
613 // same TID as the caller --> nested writer call
614 // on the same thread
615 || ( (pMutex->cWriters)
616 && (pMutex->tidWriter == tidMyself)
617 )
618 // or 3) a reader tree item was found above, and
619 // current thread is the only reader
620 || ( (pReader)
621 && (pReader->cRequests)
622 && (pReader->cRequests == pMutex->cReaders)
623 )
624 )
625 {
626 // we're safe!
627 break;
628 }
629 else
630 {
631 // we're NOT safe:
632 // this means that
633 // 1) a writer other than current thread is active, or
634 // 2) readers exist and we're not the only reader...
635 // block then until "readers done" is posted
636 ULONG ul;
637 DosResetEventSem(pMutex->hevReadersDone, &ul);
638
639 // while we're waiting on the last reader to post
640 // "readers done", release the global mutex
641 UnlockGlobal();
642 fLocked = FALSE;
643
644 // wait for all readers and writers to finish;
645 // this gets posted by
646 // -- semReleaseRead if pMutex->cReaders goes to 0
647 // -- semReleaseWrite after another writer has
648 // released its last write request
649 if (!(arc = WinWaitEventSem(pMutex->hevReadersDone, ulTimeout)))
650 // readers done:
651 // request global mutex again
652 if (!(arc = LockGlobal()))
653 fLocked = TRUE;
654 // else: probably timeout, do not loop again
655 }
656 } // end while (!arc)
657
658 if (!arc)
659 {
660 // OK, raise writers count
661 (pMutex->cWriters)++;
662 // and store our TID as the current writer
663 pMutex->tidWriter = tidMyself;
664 }
665 }
666
667 if (fLocked)
668 UnlockGlobal();
669
670 } // end if (!(arc = LockGlobal()))
671
672 return (arc);
673}
674
675/*
676 *@@ semReleaseWrite:
677 * releases write access previously requested
678 * by semRequestWrite.
679 *
680 * This may unblock other threads which have
681 * blocked in either semRequestRead or
682 * semRequestWrite.
683 */
684
685APIRET semReleaseWrite(HRW hrw) // in: rw-sem created by semCreateRWMutex
686{
687 APIRET arc = NO_ERROR;
688
689 // protect the RW by requesting the global mutex
690 if (!(arc = LockGlobal()))
691 {
692 PRWMUTEX pMutex = (PRWMUTEX)hrw;
693
694 if (!pMutex)
695 arc = ERROR_INVALID_PARAMETER;
696 else
697 {
698 // get our own thread ID
699 ULONG tidMyself = doshMyTID();
700
701 if ( (pMutex->cWriters)
702 && (pMutex->tidWriter == tidMyself)
703 )
704 {
705 (pMutex->cWriters)--;
706
707 if (pMutex->cWriters == 0)
708 {
709 ULONG ul;
710 // last write request released:
711 // post the "writer done" semaphore
712 DosResetEventSem(pMutex->hevWriterDone, &ul);
713 DosPostEventSem(pMutex->hevWriterDone);
714 // this sets all threads blocking
715 // in semRequestRead to "ready"
716
717 // and post the "reader done" semaphore
718 // as well, in case another thread is
719 // waiting for write request
720 DosResetEventSem(pMutex->hevReadersDone, &ul);
721 DosPostEventSem(pMutex->hevReadersDone);
722 // this sets all threads blocking
723 // in semRequestWrite to "ready"
724
725 // and set tidWriter to 0
726 pMutex->tidWriter = 0;
727 }
728 // else: nested write request on this
729 // thread (there can only ever be one
730 // writer thread)
731 }
732 else
733 // excessive releases for this thread,
734 // or this wasn't requested at all:
735 arc = ERROR_NOT_OWNER;
736 }
737
738 UnlockGlobal();
739
740 } // end if (!(arc = LockGlobal()))
741
742 return (arc);
743}
744
745/*
746 *@@ semQueryWrite:
747 * checks if the thread currently has write
748 * access to the read-write semaphore.
749 *
750 * Returns:
751 *
752 * -- NO_ERROR if the same thread request
753 * write access before and thus is a writer.
754 *
755 * -- ERROR_NOT_OWNER if the thread does not
756 * currently have write access.
757 */
758
759APIRET semQueryWrite(HRW hrw) // in: rw-sem created by semCreateRWMutex
760{
761 APIRET arc = NO_ERROR;
762
763 // protect the RW by requesting the global mutex
764 if (!(arc = LockGlobal()))
765 {
766 PRWMUTEX pMutex = (PRWMUTEX)hrw;
767
768 if (!pMutex)
769 arc = ERROR_INVALID_PARAMETER;
770 else
771 {
772 // get our own thread ID
773 ULONG tidMyself = doshMyTID();
774
775 if ( (pMutex->cWriters == 0)
776 || (pMutex->tidWriter != tidMyself)
777 )
778 arc = ERROR_NOT_OWNER;
779 }
780
781 UnlockGlobal();
782
783 } // end if (!(arc = LockGlobal()))
784
785 return (arc);
786}
787
788
Note: See TracBrowser for help on using the repository browser.