diff options
| author | rpj <rpj> | 2001-02-06 05:44:38 +0000 | 
|---|---|---|
| committer | rpj <rpj> | 2001-02-06 05:44:38 +0000 | 
| commit | 25f0d94be4e63b1a3cea1844bc4be7849c452a75 (patch) | |
| tree | a124e65563367a4f2d3780128e29a7bddddf2b4e | |
| parent | 9171eeaa77aaa6c4c34a88f5305ee3ebbc63077c (diff) | |
Created experimental branch.
| -rw-r--r-- | ANNOUNCE | 6 | ||||
| -rw-r--r-- | CONTRIBUTORS | 6 | ||||
| -rw-r--r-- | ChangeLog | 30 | ||||
| -rw-r--r-- | README.CV | 1732 | ||||
| -rw-r--r-- | condvar.c | 436 | ||||
| -rw-r--r-- | implement.h | 49 | ||||
| -rw-r--r-- | misc.c | 29 | ||||
| -rw-r--r-- | mutex.c | 565 | ||||
| -rw-r--r-- | nonportable.c | 26 | ||||
| -rw-r--r-- | pthread.def | 13 | ||||
| -rw-r--r-- | pthread.h | 205 | ||||
| -rw-r--r-- | rwlock.c | 8 | ||||
| -rw-r--r-- | sched.c | 67 | ||||
| -rw-r--r-- | tsd.c | 8 | 
14 files changed, 2613 insertions, 567 deletions
| @@ -219,6 +219,10 @@ The following functions are implemented:        pthread_attr_setschedparam  
        pthread_getschedparam
        pthread_setschedparam
 +      pthread_getconcurrency
 +      pthread_getconcurrency
 +      pthread_attr_getscope
 +      pthread_attr_setscope
        sched_get_priority_max (POSIX 1b)
        sched_get_priority_min (POSIX 1b)
        sched_yield            (POSIX 1b)
 @@ -265,10 +269,8 @@ The following functions are not implemented:        ---------------------------
        pthread_attr_getinheritsched
        pthread_attr_getschedpolicy
 -      pthread_attr_getscope
        pthread_attr_setinheritsched
        pthread_attr_setschedpolicy
 -      pthread_attr_setscope
        pthread_mutex_getprioceiling
        pthread_mutex_setprioceiling
        pthread_mutex_attr_getprioceiling
 diff --git a/CONTRIBUTORS b/CONTRIBUTORS index aa4dd3e..ce30926 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,5 +1,8 @@  Contributors (in approximate order of appearance)
 +[See also the ChangeLog file where individuals are
 +attributed in log entries. Likewise in the FAQ file.]
 +
  Ben Elliston		bje@cygnus.com
  Ross Johnson		rpj@ise.canberra.edu.au
  Robert Colquhoun	rjc@trump.net.au
 @@ -26,4 +29,5 @@ David Baggett	        dmb.itasoftware.com  Paul Redondo	        paul.matchvision.com
  Scott McCaskill	        scott.3dfx.om
  Thomas Pfaff            tpfaff@gmx.net
 -Franco Bez              ???
 +Franco Bez              franco.bez@gmx.de
 +Alexander Terekhov      TEREKHOV@de.ibm.com
 @@ -1,3 +1,33 @@ +2001-02-06  Ross Johnson  <rpj@setup1.ise.canberra.edu.au> + +	* condvar.c (pthread_cond_init): Completely revamped. +	(pthread_cond_destroy): Likewise. +	(ptw32_cond_wait_cleanup): Likewise. +	(ptw32_cond_timedwait): Likewise. +	(ptw32_cond_unblock): New general signaling routine. +	(pthread_cond_signal): Now calls ptw32_cond_unblock. +	(pthread_cond_broadcast): Likewise. +	implement.h (pthread_cond_t_): Revamped. +	README.CV: New; explanation of the above changes. +	- Alexander Terekhov <TEREKHOV@de.ibm.com> + +2001-02-05  Ross Johnson  <rpj@setup1.ise.canberra.edu.au> + +	* sched.c (pthread_getconcurrency): New no-op function. +	* sched.c (pthread_setconcurrency): New no-op function. +	* sched.c (pthread_attr_getscope): New no-op function. +	* sched.c (pthread_attr_setscope): New no-op function. +	* sched.c (ENOSUP): Removed define and changed references +	to use ENOTSUP. +	* pthread.h (PTHREAD_SCOPE_SYSTEM): Defined. +	* pthread.h (PTHREAD_SCOPE_PROCESS): Defined. +	* pthread.h (ENOTSUP): Defined - not defined by Win32. + +2001-01-10  Ross Johnson  <rpj@setup1.ise.canberra.edu.au> + +	* rwlock.c (ptw32_rwlock_cancelwrwait): Renamed. +	(ptw32_rwlock_cancelrdwait): Renamed. +  2000-12-29  Ross Johnson  <rpj@special.ise.canberra.edu.au>  	* Makefile: Back-out "for" loops which don't work. diff --git a/README.CV b/README.CV new file mode 100644 index 0000000..1ce222a --- /dev/null +++ b/README.CV @@ -0,0 +1,1732 @@ +README.CV -- Condition Variables +-------------------------------- + +The original implementation of condition variables in +pthreads-win32 was based on a discussion paper: + +"Strategies for Implementing POSIX Condition Variables +on Win32": http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + +The changes suggested below were made on Feb 6 2001. This +file is included in the package for the benefit of anyone +interested in understanding the pthreads-win32 implementation +of condition variables and the (sometimes subtle) issues that +it attempts to resolve. + +Thanks go to the individuals whose names appear throughout +the following text. + +Ross Johnson + +-------------------------------------------------------------------- + +fyi.. (more detailed problem description/demos + possible fix/patch) + +regards, +alexander. + + +Alexander Terekhov +31.01.2001 17:43 + +To:   ace-bugs@cs.wustl.edu +cc: +From: Alexander Terekhov/Germany/IBM@IBMDE +Subject:  Implementation of POSIX CVs: spur.wakeups/lost +      signals/deadlocks/unfairness + + + +    ACE VERSION: + +        5.1.12 (pthread-win32 snapshot 2000-12-29) + +    HOST MACHINE and OPERATING SYSTEM: + +        IBM IntelliStation Z Pro, 2 x XEON 1GHz, Win2K + +    TARGET MACHINE and OPERATING SYSTEM, if different from HOST: +    COMPILER NAME AND VERSION (AND PATCHLEVEL): + +        Microsoft Visual C++ 6.0 + +    AREA/CLASS/EXAMPLE AFFECTED: + +        Implementation of POSIX condition variables - OS.cpp/.h + +    DOES THE PROBLEM AFFECT: + +        EXECUTION? YES! + +    SYNOPSIS: + +        a) spurious wakeups (minor problem) +        b) lost signals +        c) broadcast deadlock +        d) unfairness (minor problem) + +    DESCRIPTION: + +        Please see attached copy of discussion thread +        from comp.programming.threads for more details on +        some reported problems. (i've also posted a "fyi" +        message to ace-users a week or two ago but +        unfortunately did not get any response so far). + +        It seems that current implementation suffers from +        two essential problems: + +        1) cond.waiters_count does not accurately reflect +           number of waiters blocked on semaphore - w/o +           proper synchronisation that could result (in the +           time window when counter is not accurate) +           in spurious wakeups organised by subsequent +           _signals  and _broadcasts. + +        2) Always having (with no e.g. copy_and_clear/..) +           the same queue in use (semaphore+counter) +           neither signal nor broadcast provide 'atomic' +           behaviour with respect to other threads/subsequent +           calls to signal/broadcast/wait. + +        Each problem and combination of both could produce +        various nasty things: + +        a) spurious wakeups (minor problem) + +             it is possible that waiter(s) which was already +             unblocked even so is still counted as blocked +             waiter. signal and broadcast will release +             semaphore which will produce a spurious wakeup +             for a 'real' waiter coming later. + +        b) lost signals + +             signalling thread ends up consuming its own +             signal. please see demo/discussion below. + +        c) broadcast deadlock + +             last_waiter processing code does not correctly +             handle the case with multiple threads +             waiting for the end of broadcast. +             please see demo/discussion below. + +        d) unfairness (minor problem) + +             without SignalObjectAndWait some waiter(s) +             may end up consuming broadcasted signals +             multiple times (spurious wakeups) because waiter +             thread(s) can be preempted before they call +             semaphore wait (but after count++ and mtx.unlock). + +    REPEAT BY: + +        See below... run problem demos programs (tennis.cpp and +        tennisb.cpp) number of times concurrently (on multiprocessor) +        and in multiple sessions or just add a couple of "Sleep"s +        as described in the attached copy of discussion thread +        from comp.programming.threads + +    SAMPLE FIX/WORKAROUND: + +        See attached patch to pthread-win32.. well, I can not +        claim that it is completely bug free but at least my +        test and tests provided by pthreads-win32 seem to work. +        Perhaps that will help. + +        regards, +        alexander. + + +>> Forum: comp.programming.threads +>> Thread: pthread_cond_* implementation questions +. +. +. +David Schwartz <davids@webmaster.com> wrote: + +> terekhov@my-deja.com wrote: +> +>> BTW, could you please also share your view on other perceived +>> "problems" such as nested broadcast deadlock, spurious wakeups +>> and (the latest one) lost signals?? +> +>I'm not sure what you mean. The standard allows an implementation +>to do almost whatever it likes. In fact, you could implement +>pthread_cond_wait by releasing the mutex, sleeping a random +>amount of time, and then reacquiring the mutex. Of course, +>this would be a pretty poor implementation, but any code that +>didn't work under that implementation wouldn't be strictly +>compliant. + +The implementation you suggested is indeed correct +one (yes, now I see it :). However it requires from +signal/broadcast nothing more than to "{ return 0; }" +That is not the case for pthread-win32 and ACE +implementations. I do think that these implementations +(basically the same implementation) have some serious +problems with wait/signal/broadcast calls. I am looking +for help to clarify whether these problems are real +or not. I think that I can demonstrate what I mean +using one or two small sample programs. +. +. +. +========== +tennis.cpp +========== + +#include "ace/Synch.h" +#include "ace/Thread.h" + +enum GAME_STATE { + +  START_GAME, +  PLAYER_A,     // Player A playes the ball +  PLAYER_B,     // Player B playes the ball +  GAME_OVER, +  ONE_PLAYER_GONE, +  BOTH_PLAYERS_GONE + +}; + +enum GAME_STATE             eGameState; +ACE_Mutex*                  pmtxGameStateLock; +ACE_Condition< ACE_Mutex >* pcndGameStateChange; + +void* +  playerA( +    void* pParm +  ) +{ + +  // For access to game state variable +  pmtxGameStateLock->acquire(); + +  // Play loop +  while ( eGameState < GAME_OVER ) { + +    // Play the ball +    cout << endl << "PLAYER-A" << endl; + +    // Now its PLAYER-B's turn +    eGameState = PLAYER_B; + +    // Signal to PLAYER-B that now it is his turn +    pcndGameStateChange->signal(); + +    // Wait until PLAYER-B finishes playing the ball +    do { + +      pcndGameStateChange->wait(); + +      if ( PLAYER_B == eGameState ) +        cout << endl << "----PLAYER-A: SPURIOUS WAKEUP!!!" << endl; + +    } while ( PLAYER_B == eGameState ); + +  } + +  // PLAYER-A gone +  eGameState = (GAME_STATE)(eGameState+1); +  cout << endl << "PLAYER-A GONE" << endl; + +  // No more access to state variable needed +  pmtxGameStateLock->release(); + +  // Signal PLAYER-A gone event +  pcndGameStateChange->broadcast(); + +  return 0; + +} + +void* +  playerB( +    void* pParm +  ) +{ + +  // For access to game state variable +  pmtxGameStateLock->acquire(); + +  // Play loop +  while ( eGameState < GAME_OVER ) { + +    // Play the ball +    cout << endl << "PLAYER-B" << endl; + +    // Now its PLAYER-A's turn +    eGameState = PLAYER_A; + +    // Signal to PLAYER-A that now it is his turn +    pcndGameStateChange->signal(); + +    // Wait until PLAYER-A finishes playing the ball +    do { + +      pcndGameStateChange->wait(); + +      if ( PLAYER_A == eGameState ) +        cout << endl << "----PLAYER-B: SPURIOUS WAKEUP!!!" << endl; + +    } while ( PLAYER_A == eGameState ); + +  } + +  // PLAYER-B gone +  eGameState = (GAME_STATE)(eGameState+1); +  cout << endl << "PLAYER-B GONE" << endl; + +  // No more access to state variable needed +  pmtxGameStateLock->release(); + +  // Signal PLAYER-B gone event +  pcndGameStateChange->broadcast(); + +  return 0; + +} + + +int +main (int, ACE_TCHAR *[]) +{ + +  pmtxGameStateLock   = new ACE_Mutex(); +  pcndGameStateChange = new ACE_Condition< ACE_Mutex >( *pmtxGameStateLock +); + +  // Set initial state +  eGameState = START_GAME; + +  // Create players +  ACE_Thread::spawn( playerA ); +  ACE_Thread::spawn( playerB ); + +  // Give them 5 sec. to play +  Sleep( 5000 );//sleep( 5 ); + +  // Set game over state +  pmtxGameStateLock->acquire(); +  eGameState = GAME_OVER; + +  // Let them know +  pcndGameStateChange->broadcast(); + +  // Wait for players to stop +  do { + +    pcndGameStateChange->wait(); + +  } while ( eGameState < BOTH_PLAYERS_GONE ); + +  // Cleanup +  cout << endl << "GAME OVER" << endl; +  pmtxGameStateLock->release(); +  delete pcndGameStateChange; +  delete pmtxGameStateLock; + +  return 0; + +} + +=========== +tennisb.cpp +=========== +#include "ace/Synch.h" +#include "ace/Thread.h" + +enum GAME_STATE { + +  START_GAME, +  PLAYER_A,     // Player A playes the ball +  PLAYER_B,     // Player B playes the ball +  GAME_OVER, +  ONE_PLAYER_GONE, +  BOTH_PLAYERS_GONE + +}; + +enum GAME_STATE             eGameState; +ACE_Mutex*                  pmtxGameStateLock; +ACE_Condition< ACE_Mutex >* pcndGameStateChange; + +void* +  playerA( +    void* pParm +  ) +{ + +  // For access to game state variable +  pmtxGameStateLock->acquire(); + +  // Play loop +  while ( eGameState < GAME_OVER ) { + +    // Play the ball +    cout << endl << "PLAYER-A" << endl; + +    // Now its PLAYER-B's turn +    eGameState = PLAYER_B; + +    // Signal to PLAYER-B that now it is his turn +    pcndGameStateChange->broadcast(); + +    // Wait until PLAYER-B finishes playing the ball +    do { + +      pcndGameStateChange->wait(); + +      if ( PLAYER_B == eGameState ) +        cout << endl << "----PLAYER-A: SPURIOUS WAKEUP!!!" << endl; + +    } while ( PLAYER_B == eGameState ); + +  } + +  // PLAYER-A gone +  eGameState = (GAME_STATE)(eGameState+1); +  cout << endl << "PLAYER-A GONE" << endl; + +  // No more access to state variable needed +  pmtxGameStateLock->release(); + +  // Signal PLAYER-A gone event +  pcndGameStateChange->broadcast(); + +  return 0; + +} + +void* +  playerB( +    void* pParm +  ) +{ + +  // For access to game state variable +  pmtxGameStateLock->acquire(); + +  // Play loop +  while ( eGameState < GAME_OVER ) { + +    // Play the ball +    cout << endl << "PLAYER-B" << endl; + +    // Now its PLAYER-A's turn +    eGameState = PLAYER_A; + +    // Signal to PLAYER-A that now it is his turn +    pcndGameStateChange->broadcast(); + +    // Wait until PLAYER-A finishes playing the ball +    do { + +      pcndGameStateChange->wait(); + +      if ( PLAYER_A == eGameState ) +        cout << endl << "----PLAYER-B: SPURIOUS WAKEUP!!!" << endl; + +    } while ( PLAYER_A == eGameState ); + +  } + +  // PLAYER-B gone +  eGameState = (GAME_STATE)(eGameState+1); +  cout << endl << "PLAYER-B GONE" << endl; + +  // No more access to state variable needed +  pmtxGameStateLock->release(); + +  // Signal PLAYER-B gone event +  pcndGameStateChange->broadcast(); + +  return 0; + +} + + +int +main (int, ACE_TCHAR *[]) +{ + +  pmtxGameStateLock   = new ACE_Mutex(); +  pcndGameStateChange = new ACE_Condition< ACE_Mutex >( *pmtxGameStateLock +); + +  // Set initial state +  eGameState = START_GAME; + +  // Create players +  ACE_Thread::spawn( playerA ); +  ACE_Thread::spawn( playerB ); + +  // Give them 5 sec. to play +  Sleep( 5000 );//sleep( 5 ); + +  // Make some noise +  pmtxGameStateLock->acquire(); +  cout << endl << "---Noise ON..." << endl; +  pmtxGameStateLock->release(); +  for ( int i = 0; i < 100000; i++ ) +    pcndGameStateChange->broadcast(); +  cout << endl << "---Noise OFF" << endl; + +  // Set game over state +  pmtxGameStateLock->acquire(); +  eGameState = GAME_OVER; +  cout << endl << "---Stopping the game..." << endl; + +  // Let them know +  pcndGameStateChange->broadcast(); + +  // Wait for players to stop +  do { + +    pcndGameStateChange->wait(); + +  } while ( eGameState < BOTH_PLAYERS_GONE ); + +  // Cleanup +  cout << endl << "GAME OVER" << endl; +  pmtxGameStateLock->release(); +  delete pcndGameStateChange; +  delete pmtxGameStateLock; + +  return 0; + +} +. +. +. +David Schwartz <davids@webmaster.com> wrote: +>> > It's compliant +>> +>> That is really good. +> +>> Tomorrow (I have to go urgently now) I will try to +>> demonstrate the lost-signal "problem" of current +>> pthread-win32 and ACE-(variant w/o SingleObjectAndWait) +>> implementations: players start suddenly drop their balls :-) +>> (with no change in source code). +> +>Signals aren't lost, they're going to the main thread, +>which isn't coded correctly to handle them. Try this: +> +>  // Wait for players to stop +>  do { +> +>    pthread_cond_wait( &cndGameStateChange,&mtxGameStateLock ); +>printf("Main thread stole a signal\n"); +> +>  } while ( eGameState < BOTH_PLAYERS_GONE ); +> +>I bet everytime you thing a signal is lost, you'll see that printf. +>The signal isn't lost, it was stolen by another thread. + +well, you can probably loose your bet.. it was indeed stolen +by "another" thread but not the one you seem to think of. + +I think that what actually happens is the following: + +H:\SA\UXX\pt\PTHREADS\TESTS>tennis3.exe + +PLAYER-A + +PLAYER-B + +----PLAYER-B: SPURIOUS WAKEUP!!! + +PLAYER-A GONE + +PLAYER-B GONE + +GAME OVER + +H:\SA\UXX\pt\PTHREADS\TESTS> + +here you can see that PLAYER-B after playing his first +ball (which came via signal from PLAYER-A) just dropped +it down. What happened is that his signal to player A +was consumed as spurious wakeup by himself (player B). + +The implementation has a problem: + +================ +waiting threads: +================ + +{ /** Critical Section + +  inc cond.waiters_count + +} + +  /* +  /* Atomic only if using Win32 SignalObjectAndWait +  /* +  cond.mtx.release + +  /*** ^^-- A THREAD WHICH DID SIGNAL MAY ACQUIRE THE MUTEX, +  /***      GO INTO WAIT ON THE SAME CONDITION AND OVERTAKE +  /***      ORIGINAL WAITER(S) CONSUMING ITS OWN SIGNAL! + +  cond.sem.wait + +Player-A after playing game's initial ball went into +wait (called _wait) but was pre-empted before reaching +wait semaphore. He was counted as waiter but was not +actually waiting/blocked yet. + +=============== +signal threads: +=============== + +{ /** Critical Section + +  waiters_count = cond.waiters_count + +} + +  if ( waiters_count != 0 ) + +    sem.post 1 + +  endif + +Player-B after he received signal/ball from Player A +called _signal. The _signal did see that there was +one waiter blocked on the condition (Player-A) and +released the semaphore.. (but it did not unblock +Player-A because he was not actually blocked). +Player-B thread continued its execution, called _wait, +was counted as second waiter BUT was allowed to slip +through opened semaphore gate (which was opened for +Player-B) and received his own signal. Player B remained +blocked followed by Player A. Deadlock happened which +lasted until main thread came in and said game over. + +It seems to me that the implementation fails to +correctly implement the following statement +from specification: + +http://www.opengroup.org/ +onlinepubs/007908799/xsh/pthread_cond_wait.html + +"These functions atomically release mutex and cause +the calling thread to block on the condition variable +cond; atomically here means "atomically with respect +to access by another thread to the mutex and then the +condition variable". That is, if another thread is +able to acquire the mutex after the about-to-block +thread has released it, then a subsequent call to +pthread_cond_signal() or pthread_cond_broadcast() +in that thread behaves as if it were issued after +the about-to-block thread has blocked." + +Question: Am I right? + +(I produced the program output above by simply +adding ?Sleep( 1 )?: + +================ +waiting threads: +================ + +{ /** Critical Section + +  inc cond.waiters_count + +} + +  /* +  /* Atomic only if using Win32 SignalObjectAndWait +  /* +  cond.mtx.release + +Sleep( 1 ); // Win32 + +  /*** ^^-- A THREAD WHICH DID SIGNAL MAY ACQUIRE THE MUTEX, +  /***      GO INTO WAIT ON THE SAME CONDITION AND OVERTAKE +  /***      ORIGINAL WAITER(S) CONSUMING ITS OWN SIGNAL! + +  cond.sem.wait + +to the source code of pthread-win32 implementation: + +http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/ +condvar.c?rev=1.36&content-type=text/ +x-cvsweb-markup&cvsroot=pthreads-win32 + + +  /* +  * We keep the lock held just long enough to increment the count of +  * waiters by one (above). +  * Note that we can't keep it held across the +  * call to sem_wait since that will deadlock other calls +  * to pthread_cond_signal +  */ +  cleanup_args.mutexPtr = mutex; +  cleanup_args.cv = cv; +  cleanup_args.resultPtr = &result; + +  pthread_cleanup_push (ptw32_cond_wait_cleanup, (void *) +&cleanup_args); + +  if ((result = pthread_mutex_unlock (mutex)) == 0) +    {((result +Sleep( 1 ); // @AT + +      /* +      * Wait to be awakened by +      *              pthread_cond_signal, or +      *              pthread_cond_broadcast, or +      *              a timeout +      * +      * Note: +      *      ptw32_sem_timedwait is a cancelation point, +      *      hence providing the +      *      mechanism for making pthread_cond_wait a cancelation +      *      point. We use the cleanup mechanism to ensure we +      *      re-lock the mutex and decrement the waiters count +      *      if we are canceled. +      */ +      if (ptw32_sem_timedwait (&(cv->sema), abstime) == -1)         { +          result = errno; +        } +    } + +  pthread_cleanup_pop (1);  /* Always cleanup */ + + +BTW, on my system (2 CPUs) I can manage to get +signals lost even without any source code modification +if I run the tennis program many times in different +shell sessions. +. +. +. +David Schwartz <davids@webmaster.com> wrote: +>terekhov@my-deja.com wrote: +> +>> well, it might be that the program is in fact buggy. +>> but you did not show me any bug. +> +>You're right. I was close but not dead on. I was correct, however, +>that the code is buggy because it uses 'pthread_cond_signal' even +>though not any thread waiting on the condition variable can do the +>job. I was wrong in which thread could be waiting on the cv but +>unable to do the job. + +Okay, lets change 'pthread_cond_signal' to 'pthread_cond_broadcast' +but also add some noise from main() right before declaring the game +to be over (I need it in order to demonstrate another problem of +pthread-win32/ACE implementations - broadcast deadlock)... +. +. +. +It is my understanding of POSIX conditions, +that on correct implementation added noise +in form of unnecessary broadcasts from main, +should not break the tennis program. The +only 'side effect' of added noise on correct +implementation would be 'spurious wakeups' of +players (in fact they are not spurious, +players just see them as spurious) unblocked, +not by another player but by main before +another player had a chance to acquire the +mutex and change the game state variable: +. +. +. + +PLAYER-B + +PLAYER-A + +---Noise ON... + +PLAYER-B + +PLAYER-A + +. +. +. + +PLAYER-B + +PLAYER-A + +----PLAYER-A: SPURIOUS WAKEUP!!! + +PLAYER-B + +PLAYER-A + +---Noise OFF + +PLAYER-B + +---Stopping the game... + +PLAYER-A GONE + +PLAYER-B GONE + +GAME OVER + +H:\SA\UXX\pt\PTHREADS\TESTS> + +On pthread-win32/ACE implementations the +program could stall: + +. +. +. + +PLAYER-A + +PLAYER-B + +PLAYER-A + +PLAYER-B + +PLAYER-A + +PLAYER-B + +PLAYER-A + +PLAYER-B + +---Noise ON... + +PLAYER-A + +---Noise OFF +^C +H:\SA\UXX\pt\PTHREADS\TESTS> + + +The implementation has problems: + +================ +waiting threads: +================ + +{ /** Critical Section + +  inc cond.waiters_count + +} + +  /* +  /* Atomic only if using Win32 SignalObjectAndWait +  /* +  cond.mtx.release +  cond.sem.wait + +  /*** ^^-- WAITER CAN BE PREEMPTED AFTER BEING UNBLOCKED... + +{ /** Critical Section + +  dec cond.waiters_count + +  /*** ^^- ...AND BEFORE DECREMENTING THE COUNT (1) + +  last_waiter = ( cond.was_broadcast && +                    cond.waiters_count == 0 ) + +  if ( last_waiter ) + +    cond.was_broadcast = FALSE + +  endif + +} + +  if ( last_waiter ) + +    /* +    /* Atomic only if using Win32 SignalObjectAndWait +    /* +    cond.auto_reset_event_or_sem.post /* Event for Win32 +    cond.mtx.acquire + +  /*** ^^-- ...AND BEFORE CALL TO mtx.acquire (2) + +  /*** ^^-- NESTED BROADCASTS RESULT IN A DEADLOCK + + +  else + +    cond.mtx.acquire + +  /*** ^^-- ...AND BEFORE CALL TO mtx.acquire (3) + +  endif + + +================== +broadcast threads: +================== + +{ /** Critical Section + +  waiters_count = cond.waiters_count + +  if ( waiters_count != 0 ) + +    cond.was_broadcast = TRUE + +  endif + +} + +if ( waiters_count != 0 ) + +  cond.sem.post waiters_count + +  /*** ^^^^^--- SPURIOUS WAKEUPS DUE TO (1) + +  cond.auto_reset_event_or_sem.wait /* Event for Win32 + +  /*** ^^^^^--- DEADLOCK FOR FURTHER BROADCASTS IF THEY +                HAPPEN TO GO INTO WAIT WHILE PREVIOUS +                BROADCAST IS STILL IN PROGRESS/WAITING + +endif + +a) cond.waiters_count does not accurately reflect +number of waiters blocked on semaphore - that could +result (in the time window when counter is not accurate) +in spurios wakeups organised by subsequent _signals +and _broadcasts. From standard compliance point of view +that is OK but that could be a real problem from +performance/efficiency point of view. + +b) If subsequent broadcast happen to go into wait on +cond.auto_reset_event_or_sem before previous +broadcast was unblocked from cond.auto_reset_event_or_sem +by its last waiter, one of two blocked threads will +remain blocked because last_waiter processing code +fails to unblock both threads. + +In the situation with tennisb.c the Player-B was put +in a deadlock by noise (broadcast) coming from main +thread. And since Player-B holds the game state +mutex when it calls broadcast, the whole program +stalled: Player-A was deadlocked on mutex and +main thread after finishing with producing the noise +was deadlocked on mutex too (needed to declare the +game over) + +(I produced the program output above by simply +adding ?Sleep( 1 )?: + +================== +broadcast threads: +================== + +{ /** Critical Section + +  waiters_count = cond.waiters_count + +  if ( waiters_count != 0 ) + +    cond.was_broadcast = TRUE + +  endif + +} + +if ( waiters_count != 0 ) + +Sleep( 1 ); //Win32 + +  cond.sem.post waiters_count + +  /*** ^^^^^--- SPURIOUS WAKEUPS DUE TO (1) + +  cond.auto_reset_event_or_sem.wait /* Event for Win32 + +  /*** ^^^^^--- DEADLOCK FOR FURTHER BROADCASTS IF THEY +                HAPPEN TO GO INTO WAIT WHILE PREVIOUS +                BROADCAST IS STILL IN PROGRESS/WAITING + +endif + +to the source code of pthread-win32 implementation: + +http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/ +condvar.c?rev=1.36&content-type=text/ +x-cvsweb-markup&cvsroot=pthreads-win32 + +  if (wereWaiters) +    {(wereWaiters)sroot=pthreads-win32eb.cgi/pthreads/Yem...m +      /* +      * Wake up all waiters +      */ + +Sleep( 1 ); //@AT + +#ifdef NEED_SEM + +      result = (ptw32_increase_semaphore( &cv->sema, cv->waiters ) +                 ? 0 +                : EINVAL); + +#else /* NEED_SEM */ + +      result = (ReleaseSemaphore( cv->sema, cv->waiters, NULL ) +                 ? 0 +                : EINVAL); + +#endif /* NEED_SEM */ + +    } + +  (void) pthread_mutex_unlock(&(cv->waitersLock)); + +  if (wereWaiters && result == 0) +    {(wereWaiters +      /* +       * Wait for all the awakened threads to acquire their part of +       * the counting semaphore +       */ + +      if (WaitForSingleObject (cv->waitersDone, INFINITE) +          == WAIT_OBJECT_0) +        { +          result = 0; +        } +      else +        { +          result = EINVAL; +        } + +    } + +  return (result); + +} + +BTW, on my system (2 CPUs) I can manage to get +the program stalled even without any source code +modification if I run the tennisb program many +times in different shell sessions. + +=================== +pthread-win32 patch +=================== +struct pthread_cond_t_ { +  long            nWaitersBlocked;   /* Number of threads blocked +*/ +  long            nWaitersUnblocked; /* Number of threads unblocked +*/ +  long            nWaitersToUnblock; /* Number of threads to unblock +*/ +  sem_t           semBlockQueue;     /* Queue up threads waiting for the +*/ +                                     /*   condition to become signalled +*/ +  sem_t           semBlockLock;      /* Semaphore that guards access to +*/ +                                     /* | waiters blocked count/block queue +*/ +                                     /* +-> Mandatory Sync.LEVEL-1 +*/ +  pthread_mutex_t mtxUnblockLock;    /* Mutex that guards access to +*/ +                                     /* | waiters (to)unblock(ed) counts +*/ +                                     /* +-> Optional* Sync.LEVEL-2 +*/ +};                                   /* Opt*) for _timedwait and +cancellation*/ + +int +pthread_cond_init (pthread_cond_t * cond, const pthread_condattr_t * attr) +  int result = EAGAIN; +  pthread_cond_t cv = NULL; + +  if (cond == NULL) +    {(cond +      return EINVAL; +    } + +  if ((attr != NULL && *attr != NULL) && +      ((*attr)->pshared == PTHREAD_PROCESS_SHARED)) +    { +      /* +       * Creating condition variable that can be shared between +       * processes. +       */ +      result = ENOSYS; + +      goto FAIL0; +    } + +  cv = (pthread_cond_t) calloc (1, sizeof (*cv)); + +  if (cv == NULL) +    {(cv +      result = ENOMEM; +      goto FAIL0; +    } + +  cv->nWaitersBlocked   = 0; +  cv->nWaitersUnblocked = 0; +  cv->nWaitersToUnblock = 0; + +  if (sem_init (&(cv->semBlockLock), 0, 1) != 0) +    {(sem_init +      goto FAIL0; +    } + +  if (sem_init (&(cv->semBlockQueue), 0, 0) != 0) +    {(sem_init +      goto FAIL1; +    } + +  if (pthread_mutex_init (&(cv->mtxUnblockLock), 0) != 0) +    {(pthread_mutex_init +      goto FAIL2; +    } + + +  result = 0; + +  goto DONE; + +  /* +   * ------------- +   * Failed... +   * ------------- +   */ +FAIL2: +  (void) sem_destroy (&(cv->semBlockQueue)); + +FAIL1: +  (void) sem_destroy (&(cv->semBlockLock)); + +FAIL0: +DONE: +  *cond = cv; + +  return (result); + +}                               /* pthread_cond_init */ + +int +pthread_cond_destroy (pthread_cond_t * cond) +{ +  int result = 0; +  pthread_cond_t cv; + +  /* +   * Assuming any race condition here is harmless. +   */ +  if (cond == NULL +      || *cond == NULL) +    { +      return EINVAL; +    } + +  if (*cond != (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) +    {(*cond +      cv = *cond; + +      /* +       * Synchronize access to waiters blocked count (LEVEL-1) +       */ +      if (sem_wait(&(cv->semBlockLock)) != 0) +        {(sem_wait(&(cv->semBlockLock)) +          return errno; +        } + +      /* +       * Synchronize access to waiters (to)unblock(ed) counts (LEVEL-2) +       */ +      if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0) +        {((result +          (void) sem_post(&(cv->semBlockLock)); +          return result; +        } + +      /* +       * Check whether cv is still busy (still has waiters blocked) +       */ +      if (cv->nWaitersBlocked - cv->nWaitersUnblocked > 0) +        {(cv->nWaitersBlocked +          (void) sem_post(&(cv->semBlockLock)); +          (void) pthread_mutex_unlock(&(cv->mtxUnblockLock)); +          return EBUSY; +        } + +      /* +       * Now it is safe to destroy +       */ +      (void) sem_destroy (&(cv->semBlockLock)); +      (void) sem_destroy (&(cv->semBlockQueue)); +      (void) pthread_mutex_unlock (&(cv->mtxUnblockLock)); +      (void) pthread_mutex_destroy (&(cv->mtxUnblockLock)); + +      free(cv); +      *cond = NULL; +    } +  else +    { +      /* +       * See notes in ptw32_cond_check_need_init() above also. +       */ +      EnterCriticalSection(&ptw32_cond_test_init_lock); + +      /* +       * Check again. +       */ +      if (*cond == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) +        {(*cond +          /* +           * This is all we need to do to destroy a statically +           * initialised cond that has not yet been used (initialised). +           * If we get to here, another thread +           * waiting to initialise this cond will get an EINVAL. +           */ +          *cond = NULL; +        } +      else +        { +          /* +           * The cv has been initialised while we were waiting +           * so assume it's in use. +           */ +          result = EBUSY; +        } + +      LeaveCriticalSection(&ptw32_cond_test_init_lock); +    } + +  return (result); +} + +/* + * Arguments for cond_wait_cleanup, since we can only pass a + * single void * to it. + */ +typedef struct { +  pthread_mutex_t * mutexPtr; +  pthread_cond_t cv; +  int * resultPtr; +} ptw32_cond_wait_cleanup_args_t; + +static void +ptw32_cond_wait_cleanup(void * args) +{ +  ptw32_cond_wait_cleanup_args_t * cleanup_args = +(ptw32_cond_wait_cleanup_args_t *) args; +  pthread_cond_t cv = cleanup_args->cv; +  int * resultPtr = cleanup_args->resultPtr; +  int eLastSignal; /* enum: 1=yes 0=no -1=cancelled/timedout w/o signal(s) +*/ +  int result; + +  /* +   * Whether we got here as a result of signal/broadcast or because of +   * timeout on wait or thread cancellation we indicate that we are no +   * longer waiting. The waiter is responsible for adjusting waiters +   * (to)unblock(ed) counts (protected by unblock lock). +   * Unblock lock/Sync.LEVEL-2 supports _timedwait and cancellation. +   */ +  if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0) +    {((result +      *resultPtr = result; +      return; +    } + +  cv->nWaitersUnblocked++; + +  eLastSignal = (cv->nWaitersToUnblock == 0) ? +                   -1 : (--cv->nWaitersToUnblock == 0); + +  /* +   * No more LEVEL-2 access to waiters (to)unblock(ed) counts needed +   */ +  if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0) +    {((result +      *resultPtr = result; +      return; +    } + +  /* +   * If last signal... +   */ +  if (eLastSignal == 1) +    {(eLastSignal +     /* +      * ...it means that we have end of 'atomic' signal/broadcast +      */ +      if (sem_post(&(cv->semBlockLock)) != 0) +        {(sem_post(&(cv->semBlockLock)) +          *resultPtr = errno; +          return; +        } +    } +  /* +   * If not last signal and not timed out/cancelled wait w/o signal... +   */ +  else if (eLastSignal == 0) +    { +     /* +      * ...it means that next waiter can go through semaphore +      */ +      if (sem_post(&(cv->semBlockQueue)) != 0) +        {(sem_post(&(cv->semBlockQueue)) +          *resultPtr = errno; +          return; +        } +    } + +  /* +   * XSH: Upon successful return, the mutex has been locked and is owned +   * by the calling thread +   */ +  if ((result = pthread_mutex_lock(cleanup_args->mutexPtr)) != 0) +    {((result +      *resultPtr = result; +    } + +}                               /* ptw32_cond_wait_cleanup */ + +static int +ptw32_cond_timedwait (pthread_cond_t * cond, +                      pthread_mutex_t * mutex, +                      const struct timespec *abstime) +{ +  int result = 0; +  pthread_cond_t cv; +  ptw32_cond_wait_cleanup_args_t cleanup_args; + +  if (cond == NULL || *cond == NULL) +    {(cond +      return EINVAL; +    } + +  /* +   * We do a quick check to see if we need to do more work +   * to initialise a static condition variable. We check +   * again inside the guarded section of ptw32_cond_check_need_init() +   * to avoid race conditions. +   */ +  if (*cond == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) +    {(*cond +      result = ptw32_cond_check_need_init(cond); +    } + +  if (result != 0 && result != EBUSY) +    {(result +      return result; +    } + +  cv = *cond; + +  /* +   * Synchronize access to waiters blocked count (LEVEL-1) +   */ +  if (sem_wait(&(cv->semBlockLock)) != 0) +    {(sem_wait(&(cv->semBlockLock)) +      return errno; +    } + +  cv->nWaitersBlocked++; + +  /* +   * Thats it. Counted means waiting, no more access needed +   */ +  if (sem_post(&(cv->semBlockLock)) != 0) +    {(sem_post(&(cv->semBlockLock)) +      return errno; +    } + +  /* +   * Setup this waiter cleanup handler +   */ +  cleanup_args.mutexPtr = mutex; +  cleanup_args.cv = cv; +  cleanup_args.resultPtr = &result; + +  pthread_cleanup_push (ptw32_cond_wait_cleanup, (void *) &cleanup_args); + +  /* +   * Now we can release 'mutex' and... +   */ +  if ((result = pthread_mutex_unlock (mutex)) == 0) +    {((result + +      /* +       * ...wait to be awakened by +       *              pthread_cond_signal, or +       *              pthread_cond_broadcast, or +       *              timeout, or +       *              thread cancellation +       * +       * Note: +       * +       *      ptw32_sem_timedwait is a cancellation point, +       *      hence providing the mechanism for making +       *      pthread_cond_wait a cancellation point. +       *      We use the cleanup mechanism to ensure we +       *      re-lock the mutex and adjust (to)unblock(ed) waiters +       *      counts if we are cancelled, timed out or signalled. +       */ +      if (ptw32_sem_timedwait (&(cv->semBlockQueue), abstime) != 0) +        {(ptw32_sem_timedwait +          result = errno; +        } +    } + +  /* +   * Always cleanup +   */ +  pthread_cleanup_pop (1); + + +  /* +   * "result" can be modified by the cleanup handler. +   */ +  return (result); + +}                               /* ptw32_cond_timedwait */ + + +static int +ptw32_cond_unblock (pthread_cond_t * cond, +                    int unblockAll) +{ +  int result; +  pthread_cond_t cv; + +  if (cond == NULL || *cond == NULL) +    {(cond +      return EINVAL; +    } + +  cv = *cond; + +  /* +   * No-op if the CV is static and hasn't been initialised yet. +   * Assuming that any race condition is harmless. +   */ +  if (cv == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) +    {(cv +      return 0; +    } + +  /* +   * Synchronize access to waiters blocked count (LEVEL-1) +   */ +  if (sem_wait(&(cv->semBlockLock)) != 0) +    {(sem_wait(&(cv->semBlockLock)) +      return errno; +    } + +  /* +   * Synchronize access to waiters (to)unblock(ed) counts (LEVEL-2) +   * This sync.level supports _timedwait and cancellation +   */ +  if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0) +    {((result +      return result; +    } + +  /* +   * Adjust waiters blocked and unblocked counts (collect garbage) +   */ +  if (cv->nWaitersUnblocked != 0) +    {(cv->nWaitersUnblocked +      cv->nWaitersBlocked  -= cv->nWaitersUnblocked; +      cv->nWaitersUnblocked = 0; +    } + +  /* +   * If (after adjustment) there are still some waiters blocked counted... +   */ +  if ( cv->nWaitersBlocked > 0) +    {( +      /* +       * We will unblock first waiter and leave semBlockLock/LEVEL-1 locked +       * LEVEL-1 access is left disabled until last signal/unblock +completes +       */ +      cv->nWaitersToUnblock = (unblockAll) ? cv->nWaitersBlocked : 1; + +      /* +       * No more LEVEL-2 access to waiters (to)unblock(ed) counts needed +       * This sync.level supports _timedwait and cancellation +       */ +      if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0) +        {((result +          return result; +        } + + +      /* +       * Now, with LEVEL-2 lock released let first waiter go through +semaphore +       */ +      if (sem_post(&(cv->semBlockQueue)) != 0) +        {(sem_post(&(cv->semBlockQueue)) +          return errno; +        } +    } +  /* +   * No waiter blocked - no more LEVEL-1 access to blocked count needed... +   */ +  else if (sem_post(&(cv->semBlockLock)) != 0) +    { +      return errno; +    } +  /* +   * ...and no more LEVEL-2 access to waiters (to)unblock(ed) counts needed +too +   * This sync.level supports _timedwait and cancellation +   */ +  else +    { +      result = pthread_mutex_unlock(&(cv->mtxUnblockLock)); +    } + +  return(result); + +}                               /* ptw32_cond_unblock */ + +int +pthread_cond_wait (pthread_cond_t * cond, +                   pthread_mutex_t * mutex) +{ +  /* The NULL abstime arg means INFINITE waiting. */ +  return(ptw32_cond_timedwait(cond, mutex, NULL)); +}                               /* pthread_cond_wait */ + + +int +pthread_cond_timedwait (pthread_cond_t * cond, +                        pthread_mutex_t * mutex, +                        const struct timespec *abstime) +{ +  if (abstime == NULL) +    {(abstime +      return EINVAL; +    } + +  return(ptw32_cond_timedwait(cond, mutex, abstime)); +}                               /* pthread_cond_timedwait */ + + +int +pthread_cond_signal (pthread_cond_t * cond) +{ +  /* The '0'(FALSE) unblockAll arg means unblock ONE waiter. */ +  return(ptw32_cond_unblock(cond, 0)); +}                               /* pthread_cond_signal */ + +int +pthread_cond_broadcast (pthread_cond_t * cond) +{ +  /* The '1'(TRUE) unblockAll arg means unblock ALL waiters. */ +  return(ptw32_cond_unblock(cond, 1)); +}                               /* pthread_cond_broadcast */ + + + + +TEREKHOV@de.ibm.com on 17.01.2001 01:00:57 + +Please respond to TEREKHOV@de.ibm.com + +To:   pthreads-win32@sourceware.cygnus.com +cc:   schmidt@uci.edu +Subject:  win32 conditions: sem+counter+event = broadcast_deadlock + +      spur.wakeup/unfairness/incorrectness ?? + + + + + + + +Hi, + +Problem 1: broadcast_deadlock + +It seems that current implementation does not provide "atomic" +broadcasts. That may lead to "nested" broadcasts... and it seems +that nested case is not handled correctly -> producing a broadcast +DEADLOCK as a result. + +Scenario: + +N (>1) waiting threads W1..N are blocked (in _wait) on condition's +semaphore. + +Thread B1 calls pthread_cond_broadcast, which results in "releasing" N +W threads via incrementing semaphore counter by N (stored in +cv->waiters) BUT cv->waiters counter does not change!! The caller +thread B1 remains blocked on cv->waitersDone event (auto-reset!!) BUT +condition is not protected from starting another broadcast (when called +on another thread) while still waiting for the "old" broadcast to +complete on thread B1. + +M (>=0, <N) W threads are fast enough to go thru their _wait call and +decrement cv->waiters counter. + +L (N-M) "late" waiter W threads are a) still blocked/not returned from +their semaphore wait call or b) were preempted after sem_wait but before +lock( &cv->waitersLock ) or c) are blocked on cv->waitersLock. + +cv->waiters is still > 0 (= L). + +Another thread B2 (or some W thread from M group) calls +pthread_cond_broadcast and gains access to counter... neither a) nor b) +prevent thread B2 in pthread_cond_broadcast from gaining access to +counter and starting another broadcast ( for c) - it depends on +cv->waitersLock scheduling rules: FIFO=OK, PRTY=PROBLEM,... ) + +That call to pthread_cond_broadcast (on thread B2) will result in +incrementing semaphore by cv->waiters (=L) which is INCORRECT (all +W1..N were in fact already released by thread B1) and waiting on +_auto-reset_ event cv->waitersDone which is DEADLY WRONG (produces a +deadlock)... + +All late W1..L threads now have a chance to complete their _wait call. +Last W_L thread sets an auto-reselt event cv->waitersDone which will +release either B1 or B2 leaving one of B threads in a deadlock. + +Problem 2: spur.wakeup/unfairness/incorrectness + +It seems that: + +a) because of the same problem with counter which does not reflect the +actual number of NOT RELEASED waiters, the signal call may increment +a semaphore counter w/o having a waiter blocked on it. That will result +in (best case) spurious wake ups - performance degradation due to +unnecessary context switches and predicate re-checks and (in worth case) +unfairness/incorrectness problem - see b) + +b) neither signal nor broadcast prevent other threads - "new waiters" +(and in the case of signal, the caller thread as well) from going into +_wait and overtaking "old" waiters (already released but still not returned +from sem_wait on condition's semaphore). Win semaphore just [API DOC]: +"Maintains a count between zero and some maximum value, limiting the number +of threads that are simultaneously accessing a shared resource." Calling +ReleaseSemaphore does not imply (at least not documented) that on return +from ReleaseSemaphore all waiters will in fact become released (returned +from their Wait... call) and/or that new waiters calling Wait... afterwards +will become less importance. It is NOT documented to be an atomic release +of +waiters... And even if it would be there is still a problem with a thread +being preempted after Wait on semaphore and before Wait on cv->waitersLock +and scheduling rules for cv->waitersLock itself +(??WaitForMultipleObjects??) +That may result in unfairness/incorrectness problem as described +for SetEvent impl. in "Strategies for Implementing POSIX Condition +Variables +on Win32": http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + +Unfairness -- The semantics of the POSIX pthread_cond_broadcast function is +to wake up all threads currently blocked in wait calls on the condition +variable. The awakened threads then compete for the external_mutex. To +ensure +fairness, all of these threads should be released from their +pthread_cond_wait calls and allowed to recheck their condition expressions +before other threads can successfully complete a wait on the condition +variable. + +Unfortunately, the SetEvent implementation above does not guarantee that +all +threads sleeping on the condition variable when cond_broadcast is called +will +acquire the external_mutex and check their condition expressions. Although +the Pthreads specification does not mandate this degree of fairness, the +lack of fairness can cause starvation. + +To illustrate the unfairness problem, imagine there are 2 threads, C1 and +C2, +that are blocked in pthread_cond_wait on condition variable not_empty_ that +is guarding a thread-safe message queue. Another thread, P1 then places two +messages onto the queue and calls pthread_cond_broadcast. If C1 returns +from +pthread_cond_wait, dequeues and processes the message, and immediately +waits +again then it and only it may end up acquiring both messages. Thus, C2 will +never get a chance to dequeue a message and run. + +The following illustrates the sequence of events: + +1.   Thread C1 attempts to dequeue and waits on CV non_empty_ +2.   Thread C2 attempts to dequeue and waits on CV non_empty_ +3.   Thread P1 enqueues 2 messages and broadcasts to CV not_empty_ +4.   Thread P1 exits +5.   Thread C1 wakes up from CV not_empty_, dequeues a message and runs +6.   Thread C1 waits again on CV not_empty_, immediately dequeues the 2nd +        message and runs +7.   Thread C1 exits +8.   Thread C2 is the only thread left and blocks forever since +        not_empty_ will never be signaled + +Depending on the algorithm being implemented, this lack of fairness may +yield +concurrent programs that have subtle bugs. Of course, application +developers +should not rely on the fairness semantics of pthread_cond_broadcast. +However, +there are many cases where fair implementations of condition variables can +simplify application code. + +Incorrectness -- A variation on the unfairness problem described above +occurs +when a third consumer thread, C3, is allowed to slip through even though it +was not waiting on condition variable not_empty_ when a broadcast occurred. + +To illustrate this, we will use the same scenario as above: 2 threads, C1 +and +C2, are blocked dequeuing messages from the message queue. Another thread, +P1 +then places two messages onto the queue and calls pthread_cond_broadcast. +C1 +returns from pthread_cond_wait, dequeues and processes the message. At this +time, C3 acquires the external_mutex, calls pthread_cond_wait and waits on +the events in WaitForMultipleObjects. Since C2 has not had a chance to run +yet, the BROADCAST event is still signaled. C3 then returns from +WaitForMultipleObjects, and dequeues and processes the message in the +queue. +Thus, C2 will never get a chance to dequeue a message and run. + +The following illustrates the sequence of events: + +1.   Thread C1 attempts to dequeue and waits on CV non_empty_ +2.   Thread C2 attempts to dequeue and waits on CV non_empty_ +3.   Thread P1 enqueues 2 messages and broadcasts to CV not_empty_ +4.   Thread P1 exits +5.   Thread C1 wakes up from CV not_empty_, dequeues a message and runs +6.   Thread C1 exits +7.   Thread C3 waits on CV not_empty_, immediately dequeues the 2nd +        message and runs +8.   Thread C3 exits +9.   Thread C2 is the only thread left and blocks forever since +        not_empty_ will never be signaled + +In the above case, a thread that was not waiting on the condition variable +when a broadcast occurred was allowed to proceed. This leads to incorrect +semantics for a condition variable. + + +COMMENTS??? + +regards, +alexander. + @@ -319,7 +319,6 @@ pthread_condattr_setpshared (pthread_condattr_t * attr, int pshared)  }                               /* pthread_condattr_setpshared */ -  int  pthread_cond_init (pthread_cond_t * cond, const pthread_condattr_t * attr)       /* @@ -377,43 +376,40 @@ pthread_cond_init (pthread_cond_t * cond, const pthread_condattr_t * attr)        goto FAIL0;      } -  cv->waiters = 0; -  cv->wasBroadcast = FALSE; +  cv->nWaitersBlocked   = 0; +  cv->nWaitersUnblocked = 0; +  cv->nWaitersToUnblock = 0; -  if (sem_init (&(cv->sema), 0, 0) != 0) +  if (sem_init(&(cv->semBlockLock), 0, 1) != 0)      {        goto FAIL0;      } -  if (pthread_mutex_init (&(cv->waitersLock), NULL) != 0) + +  if (sem_init(&(cv->semBlockQueue), 0, 0) != 0)      {        goto FAIL1;      } -  cv->waitersDone = CreateEvent ( -				 0, -				 (int) FALSE,  /* manualReset  */ -				 (int) FALSE,  /* setSignaled  */ -				 NULL); - -  if (cv->waitersDone == NULL) +  if (pthread_mutex_init(&(cv->mtxUnblockLock), 0) != 0)      {        goto FAIL2;      } +    result = 0;    goto DONE;    /*     * ------------- -   * Failure Code +   * Failed...     * -------------     */  FAIL2: -  (void) pthread_mutex_destroy (&(cv->waitersLock)); +  (void) sem_destroy(&(cv->semBlockQueue));  FAIL1: -  (void) sem_destroy (&(cv->sema)); +  (void) sem_destroy(&(cv->semBlockLock));  FAIL0:  DONE: @@ -423,7 +419,6 @@ DONE:  }                               /* pthread_cond_init */ -  int  pthread_cond_destroy (pthread_cond_t * cond)       /* @@ -458,7 +453,7 @@ pthread_cond_destroy (pthread_cond_t * cond)    /*     * Assuming any race condition here is harmless.     */ -  if (cond == NULL  +  if (cond == NULL        || *cond == NULL)      {        return EINVAL; @@ -468,24 +463,42 @@ pthread_cond_destroy (pthread_cond_t * cond)      {        cv = *cond; -      if (pthread_mutex_lock(&(cv->waitersLock)) != 0) -	{ -	  return EINVAL; -	} +      /* +       * Synchronize access to waiters blocked count (LEVEL-1) +       */ +      if (sem_wait(&(cv->semBlockLock)) != 0) +        { +          return errno; +        } -      if (cv->waiters > 0) -	{ -	  (void) pthread_mutex_unlock(&(cv->waitersLock)); -	  return EBUSY; -	} +      /* +       * Synchronize access to waiters (to)unblock(ed) counts (LEVEL-2) +       */ +      if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0) +        { +          (void) sem_post(&(cv->semBlockLock)); +          return result; +        } -      (void) sem_destroy (&(cv->sema)); -      (void) CloseHandle (cv->waitersDone); -      (void) pthread_mutex_unlock(&(cv->waitersLock)); -      (void) pthread_mutex_destroy (&(cv->waitersLock)); +      /* +       * Check whether cv is still busy (still has waiters blocked) +       */ +      if (cv->nWaitersBlocked - cv->nWaitersUnblocked > 0) +        { +          (void) sem_post(&(cv->semBlockLock)); +          (void) pthread_mutex_unlock(&(cv->mtxUnblockLock)); +          return EBUSY; +        } +      /* +       * Now it is safe to destroy +       */ +      *cond = NULL; /* Invalidate it before anything else */ +      (void) sem_destroy(&(cv->semBlockLock)); +      (void) sem_destroy(&(cv->semBlockQueue)); +      (void) pthread_mutex_unlock(&(cv->mtxUnblockLock)); +      (void) pthread_mutex_destroy(&(cv->mtxUnblockLock));        free(cv); -      *cond = NULL;      }    else      { @@ -535,59 +548,76 @@ typedef struct {  static void  ptw32_cond_wait_cleanup(void * args)  { -  ptw32_cond_wait_cleanup_args_t * cleanup_args = (ptw32_cond_wait_cleanup_args_t *) args; -  pthread_mutex_t * mutexPtr = cleanup_args->mutexPtr; +  ptw32_cond_wait_cleanup_args_t * cleanup_args = +    (ptw32_cond_wait_cleanup_args_t *) args;    pthread_cond_t cv = cleanup_args->cv;    int * resultPtr = cleanup_args->resultPtr; -  int lastWaiter = FALSE; +  int eLastSignal; /* enum: 1=yes 0=no -1=cancelled/timedout w/o signal(s) */ +  int result;    /* -   * Whether we got here legitimately or because of an error we -   * indicate that we are no longer waiting. The alternative -   * will result in never signaling the broadcasting thread. +   * Whether we got here as a result of signal/broadcast or because of +   * timeout on wait or thread cancellation we indicate that we are no +   * longer waiting. The waiter is responsible for adjusting waiters +   * (to)unblock(ed) counts (protected by unblock lock). +   * Unblock lock/Sync.LEVEL-2 supports _timedwait and cancellation.     */ -  if (pthread_mutex_lock (&(cv->waitersLock)) == 0) +  if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0)      { -      /* -       * The waiter is responsible for decrementing -       * its count, protected by an internal mutex. -       */ +      *resultPtr = result; +      return; +    } + +  cv->nWaitersUnblocked++; -      cv->waiters--; +  eLastSignal = (cv->nWaitersToUnblock == 0) ? -1 : (--cv->nWaitersToUnblock == 0); -      lastWaiter = cv->wasBroadcast && (cv->waiters == 0); +  /* +   * No more LEVEL-2 access to waiters (to)unblock(ed) counts needed +   */ +  if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0) +    { +      *resultPtr = result; +      return; +    } -      if (lastWaiter) +  /* +   * If last signal... +   */ +  if (eLastSignal == 1) +    { +     /* +      * ...it means that we have end of 'atomic' signal/broadcast +      */ +      if (sem_post(&(cv->semBlockLock)) != 0)          { -          cv->wasBroadcast = FALSE; +          *resultPtr = errno; +          return;          } - -      (void) pthread_mutex_unlock (&(cv->waitersLock));      } -    /* -   * If we are the last waiter on this broadcast -   * let the thread doing the broadcast proceed +   * If not last signal and not timed out/cancelled wait w/o signal...     */ -  if (lastWaiter && !SetEvent (cv->waitersDone)) +  else if (eLastSignal == 0)      { -      *resultPtr = EINVAL; +     /* +      * ...it means that next waiter can go through semaphore +      */ +      if (sem_post(&(cv->semBlockQueue)) != 0) +        { +          *resultPtr = errno; +          return; +        }      }    /* -   * We must always regain the external mutex, even when -   * errors occur, because that's the guarantee that we give -   * to our callers. -   * -   * Note that the broadcasting thread may already own the lock. -   * The standard actually requires that the signaling thread hold -   * the lock at the time that it signals if the developer wants -   * predictable scheduling behaviour. It's up to the developer. -   * In that case all waiting threads will block here until -   * the broadcasting thread releases the lock, having been -   * notified by the last waiting thread (SetEvent call above). +   * XSH: Upon successful return, the mutex has been locked and is owned +   * by the calling thread     */ -  (void) pthread_mutex_lock (mutexPtr); +  if ((result = pthread_mutex_lock(cleanup_args->mutexPtr)) != 0) +    { +      *resultPtr = result; +    }  }  static int @@ -623,63 +653,68 @@ ptw32_cond_timedwait (pthread_cond_t * cond,    cv = *cond;    /* -   * It's not OK to increment cond->waiters while the caller locked 'mutex', -   * there may be other threads just waking up (with 'mutex' unlocked) -   * and cv->... data is not protected. +   * Synchronize access to waiters blocked count (LEVEL-1)     */ -  if (pthread_mutex_lock(&(cv->waitersLock)) != 0) +  if (sem_wait(&(cv->semBlockLock)) != 0)      { -      return EINVAL; +      return errno;      } -  cv->waiters++; +  cv->nWaitersBlocked++; -  if (pthread_mutex_unlock(&(cv->waitersLock)) != 0) +  /* +   * Thats it. Counted means waiting, no more access needed +   */ +  if (sem_post(&(cv->semBlockLock)) != 0)      { -      return EINVAL; +      return errno;      }    /* -   * We keep the lock held just long enough to increment the count of -   * waiters by one (above). -   * Note that we can't keep it held across the -   * call to sem_wait since that will deadlock other calls -   * to pthread_cond_signal +   * Setup this waiter cleanup handler     */    cleanup_args.mutexPtr = mutex;    cleanup_args.cv = cv;    cleanup_args.resultPtr = &result; -  pthread_cleanup_push (ptw32_cond_wait_cleanup, (void *) &cleanup_args); +  pthread_cleanup_push(ptw32_cond_wait_cleanup, (void *) &cleanup_args); -  if ((result = pthread_mutex_unlock (mutex)) == 0) +  /* +   * Now we can release 'mutex' and... +   */ +  if ((result = pthread_mutex_unlock(mutex)) == 0)      { +        /* -       * Wait to be awakened by +       * ...wait to be awakened by         *              pthread_cond_signal, or         *              pthread_cond_broadcast, or -       *              a timeout +       *              timeout, or +       *              thread cancellation +       * +       * Note:         * -       * Note:  -       *      ptw32_sem_timedwait is a cancelation point, -       *      hence providing the -       *      mechanism for making pthread_cond_wait a cancelation -       *      point. We use the cleanup mechanism to ensure we -       *      re-lock the mutex and decrement the waiters count -       *      if we are canceled. +       *      ptw32_sem_timedwait is a cancellation point, +       *      hence providing the mechanism for making +       *      pthread_cond_wait a cancellation point. +       *      We use the cleanup mechanism to ensure we +       *      re-lock the mutex and adjust (to)unblock(ed) waiters +       *      counts if we are cancelled, timed out or signalled.         */ -      if (ptw32_sem_timedwait (&(cv->sema), abstime) == -1) -	{ -	  result = errno; -	} +      if (ptw32_sem_timedwait(&(cv->semBlockQueue), abstime) != 0) +        { +          result = errno; +        }      } -  pthread_cleanup_pop (1);  /* Always cleanup */ +  /* +   * Always cleanup +   */ +  pthread_cleanup_pop(1); +    /*     * "result" can be modified by the cleanup handler. -   * Specifically, if we are the last waiting thread and failed -   * to notify the broadcast thread to proceed.     */    return (result); @@ -736,8 +771,11 @@ pthread_cond_wait (pthread_cond_t * cond,        * ------------------------------------------------------        */  { -  /* The NULL abstime arg means INFINITE waiting. */ +  /* +   * The NULL abstime arg means INFINITE waiting. +   */    return(ptw32_cond_timedwait(cond, mutex, NULL)); +  }                               /* pthread_cond_wait */ @@ -805,6 +843,105 @@ pthread_cond_timedwait (pthread_cond_t * cond,  }                               /* pthread_cond_timedwait */ +static int +ptw32_cond_unblock (pthread_cond_t * cond, +                    int unblockAll) +{ +  int result; +  pthread_cond_t cv; + +  if (cond == NULL || *cond == NULL) +    { +      return EINVAL; +    } + +  cv = *cond; + +  /* +   * No-op if the CV is static and hasn't been initialised yet. +   * Assuming that any race condition is harmless. +   */ +  if (cv == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) +    { +      return 0; +    } + +  /* +   * Synchronize access to waiters blocked count (LEVEL-1) +   */ +  if (sem_wait(&(cv->semBlockLock)) != 0) +    { +      return errno; +    } + +  /* +   * Synchronize access to waiters (to)unblock(ed) counts (LEVEL-2) +   * This sync.level supports _timedwait and cancellation +   */ +  if ((result = pthread_mutex_lock(&(cv->mtxUnblockLock))) != 0) +    { +      return result; +    } + +  /* +   * Adjust waiters blocked and unblocked counts (collect garbage) +   */ +  if (cv->nWaitersUnblocked != 0) +    { +      cv->nWaitersBlocked  -= cv->nWaitersUnblocked; +      cv->nWaitersUnblocked = 0; +    } + +  /* +   * If (after adjustment) there are still some waiters blocked counted... +   */ +  if ( cv->nWaitersBlocked > 0) +    { +      /* +       * We will unblock first waiter and leave semBlockLock/LEVEL-1 locked +       * LEVEL-1 access is left disabled until last signal/unblock completes +       */ +      cv->nWaitersToUnblock = (unblockAll) ? cv->nWaitersBlocked : 1; + +      /* +       * No more LEVEL-2 access to waiters (to)unblock(ed) counts needed +       * This sync.level supports _timedwait and cancellation +       */ +      if ((result = pthread_mutex_unlock(&(cv->mtxUnblockLock))) != 0) +        { +          return result; +        } + + +      /* +       * Now, with LEVEL-2 lock released let first waiter go through semaphore +       */ +      if (sem_post(&(cv->semBlockQueue)) != 0) +        { +          return errno; +        } +    } +  /* +   * No waiter blocked - no more LEVEL-1 access to blocked count needed... +   */ +  else if (sem_post(&(cv->semBlockLock)) != 0) +    { +      return errno; +    } +  /* +   * ...and no more LEVEL-2 access to waiters (to)unblock(ed) counts needed too +   * This sync.level supports _timedwait and cancellation +   */ +  else +    { +      result = pthread_mutex_unlock(&(cv->mtxUnblockLock)); +    } + +  return(result); + +}                               /* ptw32_cond_unblock */ + +  int  pthread_cond_signal (pthread_cond_t * cond)       /* @@ -847,35 +984,10 @@ pthread_cond_signal (pthread_cond_t * cond)        * ------------------------------------------------------        */  { -  int result = 0; -  pthread_cond_t cv; - -  if (cond == NULL || *cond == NULL) -    { -      return EINVAL; -    } - -  cv = *cond; - -  /* -   * No-op if the CV is static and hasn't been initialised yet. -   * Assuming that race conditions are harmless. -   */ -  if (cv == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) -    { -      return 0; -    } - -  /* -   * If there aren't any waiters, then this is a no-op. -   * Assuming that race conditions are harmless. +  /*  +   * The '0'(FALSE) unblockAll arg means unblock ONE waiter.     */ -  if (cv->waiters > 0) -    { -      result = sem_post (&(cv->sema)); -    } - -  return (result); +  return(ptw32_cond_unblock(cond, 0));  }                               /* pthread_cond_signal */ @@ -917,76 +1029,10 @@ pthread_cond_broadcast (pthread_cond_t * cond)        * ------------------------------------------------------        */  { -  int result = 0; -  int wereWaiters = FALSE; -  pthread_cond_t cv; - -  if (cond == NULL || *cond == NULL) -    { -      return EINVAL; -    } - -  cv = *cond; - -  /* -   * No-op if the CV is static and hasn't been initialised yet. -   * Assuming that any race condition is harmless. +  /*  +   * The '1'(TRUE) unblockAll arg means unblock ALL waiters.     */ -  if (cv == (pthread_cond_t) PTW32_OBJECT_AUTO_INIT) -    { -      return 0; -    } - -  if (pthread_mutex_lock(&(cv->waitersLock)) == EINVAL) -    { -      return EINVAL; -    } - -  cv->wasBroadcast = TRUE; -  wereWaiters = (cv->waiters > 0); - -  if (wereWaiters) -    { -      /* -       * Wake up all waiters -       */ - -#ifdef NEED_SEM - -      result = (ptw32_increase_semaphore( &cv->sema, cv->waiters ) -		? 0 -		: EINVAL); - -#else /* NEED_SEM */ - -      result = (ReleaseSemaphore( cv->sema, cv->waiters, NULL ) -		? 0 -		: EINVAL); - -#endif /* NEED_SEM */ - -    } - -  (void) pthread_mutex_unlock(&(cv->waitersLock)); - -  if (wereWaiters && result == 0) -    { -      /* -       * Wait for all the awakened threads to acquire their part of -       * the counting semaphore -       */ -      if (WaitForSingleObject (cv->waitersDone, INFINITE) -          == WAIT_OBJECT_0) -        { -          result = 0; -        } -      else -        { -          result = EINVAL; -        } - -    } - -  return (result); +  return(ptw32_cond_unblock(cond, 1));  } + diff --git a/implement.h b/implement.h index c6f535e..ec889e3 100644 --- a/implement.h +++ b/implement.h @@ -72,6 +72,32 @@ typedef enum {  }  PThreadDemise; + +/* + * ==================== + * ==================== + * Internal implementation of a critical section + * ==================== + * ==================== + */ + +typedef struct ptw32_cs_t_ { +  CRITICAL_SECTION cs; +  LONG lock_idx; +  int entered_count; +  int valid; +  pthread_t owner; +} ptw32_cs_t; + + +/* + * ==================== + * ==================== + * POSIX thread and attributes + * ==================== + * ==================== + */ +  struct pthread_t_ {    DWORD thread;    HANDLE threadH; @@ -122,8 +148,7 @@ struct pthread_attr_t_ {  #define PTW32_OBJECT_INVALID   NULL  struct pthread_mutex_t_ { -  HANDLE mutex; -  CRITICAL_SECTION cs; +  ptw32_cs_t cs;    int lockCount;    pthread_t ownerThread;  }; @@ -131,7 +156,7 @@ struct pthread_mutex_t_ {  struct pthread_mutexattr_t_ {    int pshared; -  int forcecs; +  int type;  }; @@ -152,7 +177,7 @@ struct ThreadParms {    void *arg;  }; - +#if 0  struct pthread_cond_t_ {    long waiters;                       /* # waiting threads             */    pthread_mutex_t waitersLock;        /* Mutex that guards access to  @@ -168,6 +193,22 @@ struct pthread_cond_t_ {  					 or broadcasting               */  }; +#else + +struct pthread_cond_t_ { +  long            nWaitersBlocked;   /* Number of threads blocked */ +  long            nWaitersUnblocked; /* Number of threads unblocked */ +  long            nWaitersToUnblock; /* Number of threads to unblock */ +  sem_t           semBlockQueue;     /* Queue up threads waiting for the */ +                                     /*   condition to become signalled */ +  sem_t           semBlockLock;      /* Semaphore that guards access to */ +                                     /* waiters blocked count/block queue */ +                                     /* +-> Mandatory Sync.LEVEL-1 */ +  pthread_mutex_t mtxUnblockLock;    /* Mutex that guards access to */ +                                     /* waiters (to)unblock(ed) counts */ +                                     /* +-> Optional* Sync.LEVEL-2 */ +};                                   /* Opt*) for _timedwait and cancellation */ +#endif  struct pthread_condattr_t_ {    int pshared; @@ -142,36 +142,9 @@ pthread_self (void)        * ------------------------------------------------------        */  { -  pthread_t self = NULL; -  DWORD lastErr; +  pthread_t self; -  /* -   * Need to ensure there always is a self. -   * -   * The following call to pthread_getspecific uses TlsGetValue. -   * Win32 functions that return indications of failure call SetLastError when -   * they fail. They generally do not call SetLastError when they succeed. The -   * TlsGetValue function is an exception to this general rule. The TlsGetValue -   * function calls SetLastError to clear a thread's last error when it -   * succeeds. -   * -   * We restore the last error if TlsGetValue succeeds. -   */ -  lastErr = GetLastError();    self = (pthread_t) pthread_getspecific (ptw32_selfThreadKey); -  if (GetLastError() == NO_ERROR) -    { -      SetLastError(lastErr); -    } -  else -    { -      /* -       * What else can we do? GetLastError will tell the -       * the caller more but this is not supposed to -       * happen. -       */ -      return(NULL); -    }    if (self == NULL)      { @@ -85,6 +85,222 @@ ptw32_mutex_check_need_init(pthread_mutex_t *mutex)    return(result);  } + +/* + * The following internal versions of *CriticalSection() + * include an implementation of TryEnterCriticalSection + * for platforms on which that function has not been + * provided by Microsoft (eg. W95/98). This allows us + * to use critical sections exclusively as the basis + * of our implementation of POSIX mutex locks. + * + * Where TryEnterCriticalSection() is provided by the + * platform, these routines act as wrappers with + * minimal additional overhead. Otherwise, these + * routines manage additional state in order to + * properly emulate TryEnterCriticalSection(). + * + * In any case, using of critical sections exclusively + * should still be much faster than using Win32 mutex + * locks as our POSIX mutex locks. + */ + +static void +ptw32_InitializeCriticalSection (ptw32_cs_t * csect) +     /* +      * ------------------------------------------------------ +      * +      * PARAMETERS +      *      csect +      *              pointer to an instance of ptw32_cs_t +      * +      * DESCRIPTION +      *      Internal implementation of InitializeCriticalSection. +      *  +      * ------------------------------------------------------ +      */ +{ +  ptw32_cs_t cs = *csect; + +  cs->owner = NULL; +  cs->lock_idx = -1; +  cs->entered_count = 0; +  InitializeCriticalSection(&cs->cs); +  cs->valid = 1; +} + +static void +ptw32_DeleteCriticalSection (ptw32_cs_t * csect) +     /* +      * ------------------------------------------------------ +      * +      * PARAMETERS +      *      csect +      *              pointer to an instance of ptw32_cs_t +      * +      * DESCRIPTION +      *      Internal implementation of DeleteCriticalSection. +      *  +      * ------------------------------------------------------ +      */ +{ +  ptw32_cs_t cs = *csect; + +  cs->valid = 0; +  DeleteCriticalSection(&cs->cs); +} + +static void +ptw32_EnterCriticalSection(ptw32_cs_t * csect) +     /* +      * ------------------------------------------------------ +      * +      * PARAMETERS +      *      csect +      *              pointer to an instance of ptw32_cs_t +      * +      * DESCRIPTION +      *      Internal implementation of EnterCriticalSection. +      *  +      * ------------------------------------------------------ +      */ +{ +  ptw32_cs_t cs = *csect; + +  if (!cs->valid) +    { +      return; +    } + +  if (NULL != ptw32_try_enter_critical_section) +    { +      EnterCriticalSection(&cs->cs); +    } +  else +    { +      while (InterlockedIncrement(&cs->lock_idx) > 0) +        { +          InterlockedDecrement(&cs->lock_idx); +          Sleep(0); +        } +      EnterCriticalSection(&cs->cs); +      cs->entered_count++; +      cs->owner = pthread_self(); +      InterlockedDecrement(&cs->lock_idx); +    } +} + +static void +ptw32_LeaveCriticalSection (ptw32_cs_t * csect) +     /* +      * ------------------------------------------------------ +      * +      * PARAMETERS +      *      csect +      *              pointer to an instance of ptw32_cs_t +      * +      * DESCRIPTION +      *      Internal implementation of LeaveCriticalSection. +      *  +      * ------------------------------------------------------ +      */ +{ +  ptw32_cs_t cs = *csect; + +  if (!cs->valid) +    { +      return; +    } + +  if (NULL != ptw32_try_enter_critical_section) +    { +      LeaveCriticalSection(&cs->cs); +    } +  else +    { +      while (InterlockedIncrement(&cs->lock_idx) > 0) +        { +          InterlockedDecrement(&cs->lock_idx); +          Sleep(0); +        } + +      LeaveCriticalSection(&cs->cs); + +      cs->entered_count--; + +      if (cs->entered_count == 0) +        { +          cs->owner = NULL; +        } + +      InterlockedDecrement(&cs->lock_idx); +    } +} + +static BOOL +ptw32_TryEnterCriticalSection (ptw32_cs_t * csect) +     /* +      * ------------------------------------------------------ +      * +      * PARAMETERS +      *      csect +      *              pointer to an instance of ptw32_cs_t +      * +      * DESCRIPTION +      *      Internal implementation of TryEnterCriticalSection. +      *  +      * RETURNS +      *      FALSE              Current thread doesn't own the +      *                         lock, +      *      TRUE               Current thread owns the lock +      *                         (if the current thread already +      *                          held the lock then we recursively +      *                          enter). +      * ------------------------------------------------------ +      */ +{ +  ptw32_cs_t cs = *csect; +  BOOL result = FALSE; + +  if (!cs->valid) +    { +      return (FALSE); +    } + +  if (NULL != ptw32_try_enter_critical_section) +    { +      result = (*ptw32_try_enter_critical_section)(&cs->cs); +    } +  else +    { +      pthread_t self = pthread_self(); + +      while (InterlockedIncrement(&cs->lock_idx) > 0) +        { +          InterlockedDecrement(&cs->lock_idx); +          Sleep(0); +        } + +      if (cs->owner == NULL || pthread_equal(cs->owner, self)) +        { +          /* The semantics of TryEnterCriticalSection +           * (according to the documentation at MS) +           * are that the CS is entered recursively +           * if the thread is the current owner. +           */ +          EnterCriticalSection(&cs->cs); +          cs->entered_count++; +          cs->owner = self; +          result = TRUE; +        } + +      InterlockedDecrement(&cs->lock_idx); +    } + +  return (result); +} + +  int  pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)  { @@ -104,82 +320,35 @@ pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)        goto FAIL0;      } -  mx->mutex = 0; +  ptw32_InitializeCriticalSection(&mx->cs);    mx->lockCount = 0;    mx->ownerThread = NULL; -  if (attr != NULL -      && *attr != NULL -      && (*attr)->pshared == PTHREAD_PROCESS_SHARED -      ) +  if (attr != NULL && *attr != NULL)      { -      /* -       * Creating mutex that can be shared between -       * processes. -       */ -#if _POSIX_THREAD_PROCESS_SHARED -      /* -       * Not implemented yet. -       */ +      mx->type = (*attr)->type; -#error ERROR [__FILE__, line __LINE__]: Process shared mutexes are not supported yet. +      if ((*attr)->pshared == PTHREAD_PROCESS_SHARED) +        { +          /* +           * Creating mutex that can be shared between +           * processes. +           */ +#if _POSIX_THREAD_PROCESS_SHARED -      mx->mutex = CreateMutex(NULL, FALSE, "FIXME FIXME FIXME"); +          /* +           * Not implemented yet. +           */ -      if (mx->mutex == 0) -	{ -	  result = EAGAIN; -	} +#error ERROR [__FILE__, line __LINE__]: Process shared mutexes are not supported yet.  #else -      result = ENOSYS; +          result = ENOSYS;  #endif /* _POSIX_THREAD_PROCESS_SHARED */ -    } -  else -    { -      if (ptw32_try_enter_critical_section != NULL -	  || (attr != NULL -	      && *attr != NULL -	      && (*attr)->forcecs == 1) -	  ) -	{ -	  /*  -	   * Create a critical section.  -	   */ -	  InitializeCriticalSection(&mx->cs); - -	  /* -	   * Check that it works ok - since InitializeCriticalSection doesn't -	   * return success or failure. -	   */ -	  if ((*ptw32_try_enter_critical_section)(&mx->cs)) -	    { -	      LeaveCriticalSection(&mx->cs); -	    } -	  else -	    { -	      DeleteCriticalSection(&mx->cs); -	      result = EAGAIN; -	    } -	} -      else -	{ -	  /* -	   * Create a mutex that can only be used within the -	   * current process -	   */ -	  mx->mutex = CreateMutex (NULL, -				   FALSE, -				   NULL); - -	  if (mx->mutex == 0) -	    { -	      result = EAGAIN; -	    } -	} +        }      }    if (result != 0 && mx != NULL) @@ -200,8 +369,7 @@ pthread_mutex_destroy(pthread_mutex_t *mutex)    int result = 0;    pthread_mutex_t mx; -  if (mutex == NULL -      || *mutex == NULL) +  if (mutex == NULL || *mutex == NULL)      {        return EINVAL;      } @@ -213,7 +381,9 @@ pthread_mutex_destroy(pthread_mutex_t *mutex)      {        mx = *mutex; -      if ((result = pthread_mutex_trylock(&mx)) == 0) +      result = pthread_mutex_trylock(&mx); + +      if (result == 0 || result == EDEADLK)          {            /*             * FIXME!!! @@ -224,20 +394,11 @@ pthread_mutex_destroy(pthread_mutex_t *mutex)             */            *mutex = NULL; -          pthread_mutex_unlock(&mx); - -          if (mx->mutex == 0) -            { -              DeleteCriticalSection(&mx->cs); -            } -          else -            { -              result = (CloseHandle (mx->mutex) ? 0 : EINVAL); -            } +          result = pthread_mutex_unlock(&mx);            if (result == 0)              { -              mx->mutex = 0; +              ptw32_DeleteCriticalSection(&mx->cs);                free(mx);              }            else @@ -321,6 +482,9 @@ pthread_mutexattr_init (pthread_mutexattr_t * attr)        result = ENOMEM;      } +  ma->pshared = PTHREAD_PROCESS_PRIVATE; +  ma->type = PTHREAD_MUTEX_DEFAULT; +    *attr = ma;    return (result); @@ -519,10 +683,130 @@ pthread_mutexattr_setpshared (pthread_mutexattr_t * attr,  int +pthread_mutexattr_settype (pthread_mutexattr_t * attr, +                           int type) +     /* +      * ------------------------------------------------------ +      * +      * PARAMETERS +      *      attr +      *              pointer to an instance of pthread_mutexattr_t +      * +      *      type +      *              must be one of: +      * +      *                      PTHREAD_MUTEX_DEFAULT +      * +      *                      PTHREAD_MUTEX_NORMAL +      * +      *                      PTHREAD_MUTEX_ERRORCHECK +      * +      *                      PTHREAD_MUTEX_RECURSIVE +      * +      * DESCRIPTION +      * The pthread_mutexattr_gettype() and +      * pthread_mutexattr_settype() functions  respectively get and +      * set the mutex type  attribute. This attribute is set in  the +      * type  parameter to these functions. The default value of the +      * type  attribute is  PTHREAD_MUTEX_DEFAULT. +      *  +      * The type of mutex is contained in the type  attribute of the +      * mutex attributes. Valid mutex types include: +      * +      * PTHREAD_MUTEX_NORMAL +      *          This type of mutex does  not  detect  deadlock.  A +      *          thread  attempting  to  relock  this mutex without +      *          first unlocking it will  deadlock.  Attempting  to +      *          unlock  a  mutex  locked  by  a  different  thread +      *          results  in  undefined  behavior.  Attempting   to +      *          unlock  an  unlocked  mutex  results  in undefined +      *          behavior. +      *  +      * PTHREAD_MUTEX_ERRORCHECK +      *          This type of  mutex  provides  error  checking.  A +      *          thread  attempting  to  relock  this mutex without +      *          first unlocking it will return with  an  error.  A +      *          thread  attempting to unlock a mutex which another +      *          thread has locked will return  with  an  error.  A +      *          thread attempting to unlock an unlocked mutex will +      *          return with an error. +      *  +      * PTHREAD_MUTEX_RECURSIVE +      *          A thread attempting to relock this  mutex  without +      *          first  unlocking  it  will  succeed in locking the +      *          mutex. The relocking deadlock which can occur with +      *          mutexes of type  PTHREAD_MUTEX_NORMAL cannot occur +      *          with this type of mutex. Multiple  locks  of  this +      *          mutex  require  the  same  number  of  unlocks  to +      *          release  the  mutex  before  another  thread   can +      *          acquire the mutex. A thread attempting to unlock a +      *          mutex which another thread has locked will  return +      *          with  an  error. A thread attempting to  unlock an +      *          unlocked mutex will return  with  an  error.  This +      *          type  of mutex is only supported for mutexes whose +      *          process        shared         attribute         is +      *          PTHREAD_PROCESS_PRIVATE. +      * +      * RESULTS +      *              0               successfully set attribute, +      *              EINVAL          'attr' or 'type' is invalid, +      * +      * ------------------------------------------------------ +      */ +{ +  int result = 0; + +  if ((attr != NULL && *attr != NULL)) +    { +      switch (type) +        { +        case PTHREAD_MUTEX_DEFAULT: +        case PTHREAD_MUTEX_NORMAL: +        case PTHREAD_MUTEX_ERRORCHECK: +        case PTHREAD_MUTEX_RECURSIVE: +          (*attr)->type = type; +          break; +        default: +          result = EINVAL; +          break; +        } +    } +  else +    { +      result = EINVAL; +    } + +  return (result); + +}                               /* pthread_mutexattr_settype */ + + +int +pthread_mutexattr_gettype (pthread_mutexattr_t * attr, +                           int type) +{ +  int result = 0; + +  if ((attr != NULL && *attr != NULL)) +    { +      result = (*attr)->type; +    } +  else +    { +      result = EINVAL; +    } + +  return (result); +} + + +int  pthread_mutex_lock(pthread_mutex_t *mutex)  {    int result = 0;    pthread_mutex_t mx; +  pthread_t self; +    if (mutex == NULL || *mutex == NULL)      { @@ -541,25 +825,49 @@ pthread_mutex_lock(pthread_mutex_t *mutex)      }    mx = *mutex; +  self = pthread_self(); -  if (result == 0) +  ptw32_EnterCriticalSection(&mx->cs); + +  switch (mx->type)      { -      if (mx->mutex == 0) -	{ -	  EnterCriticalSection(&mx->cs); -	} -      else -	{ -	  result = (WaitForSingleObject(mx->mutex, INFINITE)  -		    == WAIT_OBJECT_0) -	    ? 0 -	    : EINVAL; -	} +    case PTHREAD_MUTEX_NORMAL: +      if (pthread_equal(mx->ownerThread, self)) +        { +          ptw32_LeaveCriticalSection(&mx->cs); +          /* +           * Pretend to be deadlocked but release the +           * mutex if we are canceled. +           */ +          pthread_cleanup_push(pthread_mutex_unlock, (void *) mutex); +          while (TRUE) +            { +              Sleep(0); +            } +          pthread_cleanup_pop(1); +        } +      break; +    case PTHREAD_MUTEX_DEFAULT: +    case PTHREAD_MUTEX_ERRORCHECK: +      if (pthread_equal(mx->ownerThread, self)) +        { +          ptw32_LeaveCriticalSection(&mx->cs); +          result = EDEADLK; +        } +      break; +    case PTHREAD_MUTEX_RECURSIVE: +      /* +       * Nothing more to do. +       */ +      break; +    default: +      result = EINVAL; +      break;      }    if (result == 0)      { -      mx->ownerThread = pthread_self(); +      mx->ownerThread = self;        mx->lockCount++;      } @@ -586,39 +894,16 @@ pthread_mutex_unlock(pthread_mutex_t *mutex)     */    if (mx != (pthread_mutex_t) PTW32_OBJECT_AUTO_INIT)      { -      pthread_t self = pthread_self(); - -      if (pthread_equal(mx->ownerThread, self)) +      if (pthread_equal(mx->ownerThread, pthread_self()))  	{ -	  int oldCount = mx->lockCount; -	  pthread_t oldOwner = mx->ownerThread; - -	  if (mx->lockCount > 0) -	    { -	      mx->lockCount--; -	    } +          mx->lockCount--;  	  if (mx->lockCount == 0)  	    {  	      mx->ownerThread = NULL;  	    } -	  if (mx->mutex == 0) -	    { -	      LeaveCriticalSection(&mx->cs); -	    } -	  else -	    { -	      if (!ReleaseMutex(mx->mutex)) -		{ -		  result = EINVAL; -		  /* -		   * Put things back the way they were. -		   */ -		  mx->lockCount = oldCount; -		  mx->ownerThread = oldOwner; -		} -	    } +          ptw32_LeaveCriticalSection(&mx->cs);  	}        else  	{ @@ -638,6 +923,7 @@ pthread_mutex_trylock(pthread_mutex_t *mutex)  {    int result = 0;    pthread_mutex_t mx; +  pthread_t self;    if (mutex == NULL || *mutex == NULL)      { @@ -656,36 +942,41 @@ pthread_mutex_trylock(pthread_mutex_t *mutex)      }    mx = *mutex; +  self = pthread_self();    if (result == 0)      { -      if (mx->mutex == 0) -	{ -	  if ((*ptw32_try_enter_critical_section)(&mx->cs) != TRUE) -	    { -	      result = EBUSY; -	    } -	} +      /* +       * TryEnterCriticalSection is a little different to +       * the POSIX trylock semantics. Trylock returns +       * EBUSY even if the calling thread already owns +       * the mutex - it doesn't lock it recursively, even +       * if the mutex type is PTHREAD_MUTEX_RECURSIVE. +       */ +      if (ptw32_TryEnterCriticalSection(&mx->cs)) +        { +          /* +           * We now own the lock, but check that we don't +           * already own the mutex. +           */ +          if (pthread_equal(mx->ownerThread, self)) +            { +              ptw32_LeaveCriticalSection(&mx->cs); +              result = EBUSY; +            } +        }        else -	{ -	  DWORD status; - -	  status = WaitForSingleObject (mx->mutex, 0); - -	  if (status != WAIT_OBJECT_0) -	    { -	      result = ((status == WAIT_TIMEOUT) -			? EBUSY -			: EINVAL); -	    } -	} +        { +          result = EBUSY; +        }      }    if (result == 0)      { -      mx->ownerThread = pthread_self(); +      mx->ownerThread = self;        mx->lockCount++;      } -  return(result); +  return (result);  } + diff --git a/nonportable.c b/nonportable.c index 49548dd..17cbc60 100644 --- a/nonportable.c +++ b/nonportable.c @@ -27,30 +27,6 @@  #include "implement.h"  /* - * pthread_mutexattr_setforcecs_np() - * - * Allows an application to force the library to use - * critical sections rather than win32 mutexes as - * the basis for any mutexes that use "attr". - * - * Values for "forcecs" are defined in pthread.h - */ -int -pthread_mutexattr_setforcecs_np(pthread_mutexattr_t *attr, -				int forcecs) -{ -  if (attr == NULL || *attr == NULL) -    { -      /* This is disallowed. */ -      return EINVAL; -    } - -  (*attr)->forcecs = forcecs; - -  return 0; -} - -/*   * pthread_getw32threadhandle_np()   *   * Returns the win32 thread handle that the POSIX @@ -271,4 +247,4 @@ pthread_win32_thread_detach_np ()      }    return TRUE; -}
\ No newline at end of file +} diff --git a/pthread.def b/pthread.def index 9371ce9..580caf5 100644 --- a/pthread.def +++ b/pthread.def @@ -1,5 +1,5 @@  ; pthread.def
 -; Last updated: $Date: 2000/12/28 05:32:07 $
 +; Last updated: $Date: 2001/02/06 05:44:38 $
  ; Currently unimplemented functions are commented out.
 @@ -13,7 +13,7 @@ pthread_attr_getdetachstate  ;pthread_attr_getinheritsched
  pthread_attr_getschedparam
  ;pthread_attr_getschedpolicy
 -;pthread_attr_getscope
 +pthread_attr_getscope
  pthread_attr_getstackaddr
  pthread_attr_getstacksize
  pthread_attr_init
 @@ -21,12 +21,12 @@ pthread_attr_setdetachstate  ;pthread_attr_setinheritsched
  pthread_attr_setschedparam
  ;pthread_attr_setschedpolicy
 -;pthread_attr_setscope
 +pthread_attr_setscope
  pthread_attr_setstackaddr
  pthread_attr_setstacksize
  pthread_cancel
  ;
 -; These are implemented as macros
 +; These two are implemented as macros
  ;
  ;pthread_cleanup_pop
  ;pthread_cleanup_push
 @@ -45,6 +45,7 @@ pthread_create  pthread_detach
  pthread_equal
  pthread_exit
 +pthread_getconcurrency
  pthread_getschedparam
  pthread_getspecific
  pthread_join
 @@ -55,10 +56,12 @@ pthread_mutexattr_destroy  ;pthread_mutexattr_getprioceiling
  ;pthread_mutexattr_getprotocol
  pthread_mutexattr_getpshared
 +pthread_mutexattr_gettype
  pthread_mutexattr_init
  ;pthread_mutexattr_setprioceiling
  ;pthread_mutexattr_setprotocol
  pthread_mutexattr_setpshared
 +pthread_mutexattr_settype
  pthread_mutexattr_destroy
  pthread_mutex_init
  pthread_mutex_destroy
 @@ -69,6 +72,7 @@ pthread_once  pthread_self
  pthread_setcancelstate
  pthread_setcanceltype
 +pthread_setconcurrency
  pthread_setschedparam
  pthread_setspecific
  ;pthread_sigmask
 @@ -101,7 +105,6 @@ pthread_rwlock_unlock  ;
  ; Non-portable but useful
  ;
 -pthread_mutexattr_setforcecs_np
  pthread_getw32threadhandle_np
  pthread_delay_np
  pthreadCancelableWait
 @@ -35,157 +35,7 @@   *              POSIX 1003.1c-1995      (POSIX.1c)   *   * Authors: - *      Contributors are listed in the file "MAINTAINERS". - * - * The following functions are implemented: - *      --------------------------- - *      PThreads - *      --------------------------- - *      pthread_attr_init - *      pthread_attr_destroy - *      pthread_attr_getdetachstate - *      pthread_attr_getstackaddr - *      pthread_attr_getstacksize - *      pthread_attr_setdetachstate      - *      pthread_attr_setstackaddr - *      pthread_attr_setstacksize - * - *      pthread_create - *      pthread_detach - *      pthread_equal - *      pthread_exit - *      pthread_join - *      pthread_self - *      sched_yield - * - *      pthread_cancel - *      pthread_cleanup_pop - *      pthread_cleanup_push - *      pthread_setcancelstate - *      pthread_setcanceltype - *      pthread_testcancel - * - *      --------------------------- - *      Thread Specific Data - *      --------------------------- - *      pthread_key_create - *      pthread_key_delete - *      pthread_setspecific - *      pthread_getspecific - * - *      --------------------------- - *      Mutexes - *      --------------------------- - *      pthread_mutexattr_init - *      pthread_mutexattr_destroy - *      pthread_mutexattr_getpshared - *      pthread_mutexattr_setpshared - * - *      pthread_mutex_init - *      pthread_mutex_destroy - *      pthread_mutex_lock - *      pthread_mutex_trylock - *      pthread_mutex_unlock - * - *      --------------------------- - *      Condition Variables - *      --------------------------- - *      pthread_condattr_init - *      pthread_condattr_destroy - *      pthread_condattr_getpshared - *      pthread_condattr_setpshared - * - *      pthread_cond_init - *      pthread_cond_destroy - *      pthread_cond_wait - *      pthread_cond_timedwait - *      pthread_cond_signal - *      pthread_cond_broadcast - * - *      --------------------------- - *      Protected Methods - *      --------------------------- - *      pthreadCancelableWait - * - *      --------------------------- - *      RealTime Scheduling: - *      --------------------------- - *      pthread_attr_getschedparam - *      pthread_attr_setschedparam - *      pthread_getschedparam - *      pthread_setschedparam - *      sched_get_priority_max - *      sched_get_priority_min - * - *      --------------------------- - *      Signals: - *      --------------------------- - *      pthread_sigmask - * - *      --------------------------- - *      Read/Write Locks: - *      --------------------------- - *      pthread_rwlock_init - *      pthread_rwlock_destroy - *      pthread_rwlock_tryrdlock - *      pthread_rwlock_trywrlock - *      pthread_rwlock_rdlock - *      pthread_rwlock_rwlock - *      pthread_rwlock_unlock - * - * Limitations - * =========== - *      The following functions are not implemented: - * - *      --------------------------- - *      RealTime Scheduling: - *      --------------------------- - *      pthread_attr_getinheritsched - *      pthread_attr_getschedpolicy - *      pthread_attr_getscope - *      pthread_attr_setinheritsched - *      pthread_attr_setschedpolicy - *      pthread_attr_setscope - *      pthread_mutex_getprioceiling - *      pthread_mutex_setprioceiling - *      pthread_mutex_attr_getprioceiling - *      pthread_mutex_attr_getprotocol - *      pthread_mutex_attr_setprioceiling - *      pthread_mutex_attr_setprotocol - * - *      --------------------------- - *      Fork Handlers: - *      --------------------------- - *      pthread_atfork - * - *      --------------------------- - *      Stdio: - *      --------------------------- - *      flockfile - *      ftrylockfile - *      funlockfile - *      getc_unlocked - *      getchar_unlocked - *      putc_unlocked - *      putchar_unlocked - * - *      --------------------------- - *      Thread-Safe C Runtime Library: - *      --------------------------- - *      readdir_r - *      getgrgid_r - *      getgrnam_r - *      getpwuid_r - *      getpwnam_r - * - *      --------------------------- - *      Signals: - *      --------------------------- - *      pthread_kill - *      sigtimedwait - *      sigwait - *      sigwaitinfo - * + *      Contributors are listed in the file "CONTRIBUTORS".   *   * -------------------------------------------------------------   */ @@ -267,6 +117,14 @@ struct timespec {  #define ETIMEDOUT 10060     /* This is the value in winsock.h. */  #endif +/* + * If ENOTSUP is not defined, define it to a value that will never occur. + * This is the value used in the Win32 TCL port. + */ +#ifndef ENOTSUP +#define ENOTSUP  -1030507 +#endif +  #ifdef __MINGW32__  #define PT_STDCALL  #else @@ -495,10 +353,20 @@ typedef struct pthread_rwlockattr_t_ *pthread_rwlockattr_t;  #define PTHREAD_PROCESS_SHARED		1  /* - * pthread_mutexattr_setforcecs_np + * pthread_mutexattr_(get,set}type + */ +#define PTHREAD_MUTEX_DEFAULT           0 +#define PTHREAD_MUTEX_NORMAL            1 +#define PTHREAD_MUTEX_ERRORCHECK        2 +#define PTHREAD_MUTEX_RECURSIVE         3 + +/* + * pthread_attr_(get,set}scope + * + * PTHREAD_SCOPE_PROCESS is the only scope supported.   */ -#define PTHREAD_MUTEX_AUTO_CS_NP        0 -#define PTHREAD_MUTEX_FORCE_CS_NP       1 +#define PTHREAD_SCOPE_SYSTEM  0 +#define PTHREAD_SCOPE_PROCESS 1  /*   * ==================== @@ -556,6 +424,14 @@ struct sched_param {  }; +/* + * ==================== + * ==================== + * Cancelation cleanup + * ==================== + * ==================== + */ +  /* There are three implementations of cancel cleanup.   * Note that pthread.h is included in both application   * compilation units and also internally for the library. @@ -878,6 +754,19 @@ int pthread_attr_getschedparam (const pthread_attr_t *attr,  int pthread_attr_setschedparam (pthread_attr_t *attr,  				const struct sched_param *param); +int +pthread_setconcurrency (int); + +int +pthread_getconcurrency (void); + +int +pthread_attr_setscope (const pthread_attr_t *, int); + +int +pthread_attr_getscope (const pthread_attr_t *, +                       int *); +  /*   * Read-Write Lock Functions   */ @@ -904,10 +793,10 @@ int pthread_rwlock_unlock(pthread_rwlock_t *lock);  /* Possibly supported by other POSIX threads implimentations */  int pthread_delay_np (struct timespec * interval); -/* Pthread Win32 specific */ -int pthread_mutexattr_setforcecs_np(pthread_mutexattr_t *attr, -				    int forcecs); - +/* + * Returns the Win32 thread HANDLE associated + * with the given POSIX thread. + */  HANDLE pthread_getw32threadhandle_np(pthread_t thread);  /* @@ -231,7 +231,7 @@ pthread_rwlock_destroy(pthread_rwlock_t *rwlock)  }  static void -_rwlock_cancelrdwait(void * arg) +ptw32_rwlock_cancelrdwait(void * arg)  {      pthread_rwlock_t rw; @@ -286,7 +286,7 @@ pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)      while (rw->rw_refcount < 0 || rw->rw_nwaitwriters > 0)        {          rw->rw_nwaitreaders++; -        pthread_cleanup_push(_rwlock_cancelrdwait, rw); +        pthread_cleanup_push(ptw32_rwlock_cancelrdwait, rw);          result = pthread_cond_wait(&(rw->rw_condreaders), &(rw->rw_lock));          pthread_cleanup_pop(0);          rw->rw_nwaitreaders--; @@ -309,7 +309,7 @@ FAIL1:  }  static void -_rwlock_cancelwrwait(void * arg) +ptw32_rwlock_cancelwrwait(void * arg)  {      pthread_rwlock_t rw; @@ -360,7 +360,7 @@ pthread_rwlock_wrlock(pthread_rwlock_t * rwlock)      while (rw->rw_refcount != 0)        {          rw->rw_nwaitwriters++; -        pthread_cleanup_push(_rwlock_cancelwrwait, rw); +        pthread_cleanup_push(ptw32_rwlock_cancelwrwait, rw);          result = pthread_cond_wait(&(rw->rw_condwriters), &(rw->rw_lock));          pthread_cleanup_pop(0);          rw->rw_nwaitwriters--; @@ -23,7 +23,10 @@   * MA 02111-1307, USA   */ -#define ENOSUP 0 +/* + * If ENOTSUP is not defined, define it to a value that will never occur. + * This is the value used in the Win32 TCL port. + */  #include "pthread.h"  #include "implement.h" @@ -63,7 +66,8 @@ pthread_attr_getschedparam(const pthread_attr_t *attr,    return 0;  } -int pthread_setschedparam(pthread_t thread, int policy, +int +pthread_setschedparam(pthread_t thread, int policy,  			  const struct sched_param *param)  {    /* Validate the thread id. */ @@ -81,7 +85,7 @@ int pthread_setschedparam(pthread_t thread, int policy,    /* Ensure the policy is SCHED_OTHER. */    if (policy != SCHED_OTHER)      { -      return ENOSUP; +      return ENOTSUP;      }    /* Validate priority level. */ @@ -97,7 +101,8 @@ int pthread_setschedparam(pthread_t thread, int policy,    return 0;  } -int pthread_getschedparam(pthread_t thread, int *policy, +int +pthread_getschedparam(pthread_t thread, int *policy,  			  struct sched_param *param)  {    int prio; @@ -129,6 +134,51 @@ int pthread_getschedparam(pthread_t thread, int *policy,  } +int +pthread_setconcurrency(int level) +{ +  if (level < 0) +    { +      return EINVAL; +    } +  else +    { +      return 0; +    } +} + +int +pthread_getconcurrency(void) +{ +  return 0; +} + +int +pthread_attr_setscope(pthread_attr_t *attr, int contentionscope) +{ +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING +  if (contentionscope != PTHREAD_SCOPE_SYSTEM) +    { +      return ENOTSUP; +    } + +  return 0; +#else +  return ENOSYS; +#endif +} + +int +pthread_attr_getscope(const pthread_attr_t *attr, int *contentionscope) +{ +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING +  *contentionscope = PTHREAD_SCOPE_SYSTEM; +  return 0; +#else +  return ENOSYS; +#endif +} +  /*   * On Windows98, THREAD_PRIORITY_LOWEST is (-2) and    * THREAD_PRIORITY_HIGHEST is 2, and everything works just fine. @@ -144,7 +194,8 @@ int pthread_getschedparam(pthread_t thread, int *policy,  #define sched_Max(a,b)  ((a)<(b)?(b):(a))  #define sched_Min(a,b)  ((a)>(b)?(b):(a)) -int sched_get_priority_max(int policy) +int +sched_get_priority_max(int policy)  {    if (policy < SCHED_MIN || policy > SCHED_MAX)      { @@ -160,7 +211,8 @@ int sched_get_priority_max(int policy)  #endif  } -int sched_get_priority_min(int policy) +int +sched_get_priority_min(int policy)  {    if (policy < SCHED_MIN || policy > SCHED_MAX)      { @@ -176,7 +228,8 @@ int sched_get_priority_min(int policy)  #endif  } -int sched_yield(void) +int +sched_yield(void)       /*        * ------------------------------------------------------        * DOCPUBLIC @@ -322,6 +322,12 @@ pthread_getspecific (pthread_key_t key)        * ------------------------------------------------------        */  { -  return (TlsGetValue (key->key)); +  int lasterror = GetLastError(); + +  void *ptr = TlsGetValue(key->key); + +  SetLastError(lasterror); + +  return (ptr);  } | 
