LCOV - code coverage report
Current view: directory - src/shared/platform/win - condition_variable.cc (source / functions) Found Hit Coverage
Test: coverage.lcov Lines: 81 50 61.7 %
Date: 2014-09-25 Functions: 0 0 -

       1                 : /*
       2                 :  * Copyright (c) 2011 The Native Client Authors. All rights reserved.
       3                 :  * Use of this source code is governed by a BSD-style license that can be
       4                 :  * found in the LICENSE file.
       5                 :  */
       6                 : 
       7                 : 
       8                 : // See header file for description of class.
       9                 : // There are numerous subtle implementation details in this class.  See the FAQ
      10                 : // comments at the end of this file for that discussion.
      11                 : 
      12                 : #include "native_client/src/include/portability.h"
      13                 : #include <stack>
      14                 : 
      15                 : #include "native_client/src/shared/platform/nacl_sync.h"
      16                 : #include "native_client/src/include/nacl_macros.h"
      17                 : #include "native_client/src/shared/platform/nacl_log.h"
      18                 : #include "native_client/src/shared/platform/nacl_time.h"
      19                 : #include "native_client/src/shared/platform/win/time.h"
      20                 : #include "native_client/src/shared/platform/win/condition_variable.h"
      21                 : #include "native_client/src/shared/platform/win/lock.h"
      22                 : #include "native_client/src/trusted/service_runtime/include/sys/time.h"
      23                 : 
      24                 : 
      25                 : NaCl::ConditionVariable::ConditionVariable()
      26                 :   : run_state_(RUNNING),
      27                 :     allocation_counter_(0),
      28               2 :     recycling_list_size_(0) {
      29               2 : }
      30                 : 
      31               2 : NaCl::ConditionVariable::~ConditionVariable() {
      32               2 :   AutoLock auto_lock(internal_lock_);
      33               2 :   run_state_ = SHUTDOWN;  // Prevent any more waiting.
      34                 : 
      35                 :   // DCHECK(recycling_list_size_ == allocation_counter_) ;
      36               2 :   if (recycling_list_size_ != allocation_counter_) {  // Rare shutdown problem.
      37                 :     // User left threads Wait()ing, and is destructing us.
      38                 :     // Note: waiting_list_ *might* be empty, but recycling is still pending.
      39               0 :     AutoUnlock auto_unlock(internal_lock_);
      40               0 :     Broadcast();  // Make sure all waiting threads have been signaled.
      41               0 :     Sleep(10);  // Give threads a chance to grab internal_lock_.
      42                 :     // All contained threads should be blocked on user_lock_ by now :-).
      43               0 :   }  // Re-Acquire internal_lock_
      44                 : 
      45                 :   // DCHECK(recycling_list_size_ == allocation_counter_); //We couldn't fix it.
      46                 : 
      47               2 :   while (!recycling_list_.IsEmpty()) {
      48                 :     ConditionVariableEvent* cv_event;
      49               2 :     cv_event = recycling_list_.PopFront();
      50               2 :     recycling_list_size_--;
      51               2 :     delete cv_event;
      52               2 :     allocation_counter_--;
      53               2 :   }
      54                 :   // DCHECK(0 == recycling_list_size_);
      55                 :   // DCHECK(0 == allocation_counter_);
      56               2 : }
      57                 : 
      58                 : // Wait() atomically releases the caller's lock as it starts to Wait, and then
      59                 : // re-acquires it when it is signaled.
      60               2 : int NaCl::ConditionVariable::TimedWaitRel(Lock& user_lock, TimeDelta max_time) {
      61                 :   ConditionVariableEvent* waiting_event;
      62                 :   HANDLE  handle;
      63                 :   DWORD   result;
      64                 :   { /* SCOPE */
      65               2 :     AutoLock auto_lock(internal_lock_);
      66               2 :     if (RUNNING != run_state_) return 0;  // Destruction in progress.
      67               2 :     waiting_event = GetEventForWaiting();
      68               2 :     handle = waiting_event->handle();
      69                 :     // DCHECK(0 != handle);
      70               2 :   }  // Release internal_lock.
      71                 : 
      72                 :   { /* SCOPE */
      73               2 :     AutoUnlock unlock(user_lock);  // Release caller's lock
      74                 :     result = WaitForSingleObject(handle,
      75               2 :                                  static_cast<DWORD>(max_time.InMilliseconds()));
      76                 :     // Minimize spurious signal creation window by recycling asap.
      77               2 :     AutoLock auto_lock(internal_lock_);
      78               2 :     RecycleEvent(waiting_event);
      79                 :     // Release internal_lack_
      80               2 :   }  // Re-Acquire callers lock to depth at entry.
      81               2 :   if (WAIT_OBJECT_0 == result)
      82               2 :     return 1;  // the object was signaled
      83               0 :   return 0;
      84               2 : }
      85                 : 
      86               0 : static NaCl::TimeTicks NaClWinUnixTimeBaseNow() {
      87                 :   struct nacl_abi_timeval unix_time;
      88                 : 
      89               0 :   (void) NaClGetTimeOfDay(&unix_time);
      90                 :   return NaCl::TimeTicks(
      91                 :       unix_time.nacl_abi_tv_sec * NaCl::Time::kMicrosecondsPerSecond
      92               0 :       + unix_time.nacl_abi_tv_usec);
      93               0 : }
      94                 : 
      95               0 : int NaCl::ConditionVariable::TimedWaitAbs(Lock& user_lock, TimeTicks abs_time) {
      96                 : #if 0
      97                 :   // Test the unit test
      98                 :   TimeDelta relative_time = TimeDelta::FromDays(1);
      99                 : #else
     100               0 :   TimeDelta relative_time = abs_time - NaClWinUnixTimeBaseNow();
     101                 : #endif
     102                 :   NaClLog(4,
     103                 :           "TimedWaitAbs: req'd abs_time %" NACL_PRId64 " ticks (uS)\n",
     104               0 :           abs_time.ticks_for_testing());
     105                 :   NaClLog(4,
     106                 :           "TimedWaitAbs: relative_time  %" NACL_PRId64 " ticks (uS)\n",
     107               0 :           relative_time.InMicroseconds());
     108               0 :   if (relative_time.InMicroseconds() < 0) {
     109               0 :     NaClLog(4, "TimedWaitAbs: time already passed!\n");
     110                 :     // we check to see if the object has been signaled anyway
     111               0 :     relative_time = TimeDelta::FromMicroseconds(0);
     112                 :   }
     113               0 :   return TimedWaitRel(user_lock, relative_time);
     114               0 : }
     115                 : 
     116                 : // Broadcast() is guaranteed to signal all threads that were waiting (i.e., had
     117                 : // a cv_event internally allocated for them) before Broadcast() was called.
     118               0 : void NaCl::ConditionVariable::Broadcast() {
     119               0 :   std::stack<HANDLE> handles;  // See FAQ-question-10.
     120                 :   { /* SCOPE */
     121               0 :     AutoLock auto_lock(internal_lock_);
     122               0 :     if (waiting_list_.IsEmpty())
     123               0 :       return;
     124               0 :     while (!waiting_list_.IsEmpty())
     125                 :       // This is not a leak from waiting_list_.  See FAQ-question 12.
     126               0 :       handles.push(waiting_list_.PopBack()->handle());
     127               0 :   }  // Release internal_lock_.
     128               0 :   while (handles.size() > 0) {
     129               0 :     SetEvent(handles.top());
     130               0 :     handles.pop();
     131               0 :   }
     132               0 : }
     133                 : 
     134                 : // Signal() will select one of the waiting threads, and signal it (signal its
     135                 : // cv_event).  For better performance we signal the thread that went to sleep
     136                 : // most recently (LIFO).  If we want fairness, then we wake the thread that has
     137                 : // been sleeping the longest (FIFO).
     138               2 : void NaCl::ConditionVariable::Signal() {
     139                 :   HANDLE handle;
     140                 :   { /* SCOPE */
     141               2 :     AutoLock auto_lock(internal_lock_);
     142               2 :     if (waiting_list_.IsEmpty())
     143               2 :       return;  // No one to signal.
     144                 : 
     145                 : #ifndef FAIRNESS
     146                 :     // Only performance option should be used.
     147                 :     // This is not a leak from waiting_list.  See FAQ-question 12.
     148               2 :      handle = waiting_list_.PopBack()->handle();  // LIFO.
     149                 : #else
     150                 :      handle = waiting_list_.PopFront()->handle();  // FIFO.
     151                 : #endif  // FAIRNESS
     152               2 :   }  // Release internal_lock_.
     153               2 :   SetEvent(handle);
     154               2 : }
     155                 : 
     156                 : // GetEventForWaiting() provides a unique cv_event for any caller that needs to
     157                 : // wait.  This means that (worst case) we may over time create as many cv_event
     158                 : // objects as there are threads simultaneously using this instance's Wait()
     159                 : // functionality .
     160               2 : NaCl::ConditionVariableEvent* NaCl::ConditionVariable::GetEventForWaiting() {
     161                 :   // We hold internal_lock, courtesy of Wait().
     162                 :   ConditionVariableEvent* cv_event;
     163               2 :   if (0 == recycling_list_size_) {
     164                 :     // DCHECK(recycling_list_.IsEmpty());
     165               2 :     cv_event = new ConditionVariableEvent(true);
     166                 :     // CHECK(cv_event);
     167               2 :     allocation_counter_++;
     168                 :     // CHECK(0 != cv_event->handle());
     169               2 :   } else {
     170               2 :     cv_event = recycling_list_.PopFront();
     171               2 :     recycling_list_size_--;
     172                 :   }
     173               2 :   waiting_list_.PushBack(cv_event);
     174               2 :   return cv_event;
     175               2 : }
     176                 : 
     177                 : // RecycleEvent() takes a cv_event that was previously used for Wait()ing, and
     178                 : // recycles it for use in future Wait() calls for this or other threads.
     179                 : // Note that there is a tiny chance that the cv_event is still signaled when we
     180                 : // obtain it, and that can cause spurious signals (if/when we re-use the
     181                 : // cv_event), but such is quite rare (see FAQ-question-5).
     182               2 : void NaCl::ConditionVariable::RecycleEvent(ConditionVariableEvent* used_event) {
     183                 :   // We hold internal_lock, courtesy of Wait().
     184                 :   // If the cv_event timed out, then it is necessary to remove it from
     185                 :   // waiting_list_.  If it was selected by Broadcast() or Signal(), then it is
     186                 :   // already gone.
     187               2 :   used_event->Extract();  // Possibly redundant
     188               2 :   recycling_list_.PushBack(used_event);
     189               2 :   recycling_list_size_++;
     190               2 : }
     191                 : 
     192                 : /*
     193                 : FAQ On subtle implementation details:
     194                 : 
     195                 : 1) What makes this problem subtle?  Please take a look at "Strategies
     196                 : for Implementing POSIX Condition Variables on Win32" by Douglas
     197                 : C. Schmidt and Irfan Pyarali.
     198                 : http://www.cs.wustl.edu/~schmidt/win32-cv-1.html It includes
     199                 : discussions of numerous flawed strategies for implementing this
     200                 : functionality.  I'm not convinced that even the final proposed
     201                 : implementation has semantics that are as nice as this implementation
     202                 : (especially with regard to Broadcast() and the impact on threads that
     203                 : try to Wait() after a Broadcast() has been called, but before all the
     204                 : original waiting threads have been signaled).
     205                 : 
     206                 : 2) Why can't you use a single wait_event for all threads that call
     207                 : Wait()?  See FAQ-question-1, or consider the following: If a single
     208                 : event were used, then numerous threads calling Wait() could release
     209                 : their cs locks, and be preempted just before calling
     210                 : WaitForSingleObject().  If a call to Broadcast() was then presented on
     211                 : a second thread, it would be impossible to actually signal all
     212                 : waiting(?) threads.  Some number of SetEvent() calls *could* be made,
     213                 : but there could be no guarantee that those led to to more than one
     214                 : signaled thread (SetEvent()'s may be discarded after the first!), and
     215                 : there could be no guarantee that the SetEvent() calls didn't just
     216                 : awaken "other" threads that hadn't even started waiting yet (oops).
     217                 : Without any limit on the number of requisite SetEvent() calls, the
     218                 : system would be forced to do many such calls, allowing many new waits
     219                 : to receive spurious signals.
     220                 : 
     221                 : 3) How does this implementation cause spurious signal events?  The
     222                 : cause in this implementation involves a race between a signal via
     223                 : time-out and a signal via Signal() or Broadcast().  The series of
     224                 : actions leading to this are:
     225                 : 
     226                 : a) Timer fires, and a waiting thread exits the line of code:
     227                 : 
     228                 :     WaitForSingleObject(waiting_event, max_time.InMilliseconds());
     229                 : 
     230                 : b) That thread (in (a)) is randomly pre-empted after the above line,
     231                 : leaving the waiting_event reset (unsignaled) and still in the
     232                 : waiting_list_.
     233                 : 
     234                 : c) A call to Signal() (or Broadcast()) on a second thread proceeds, and
     235                 : selects the waiting cv_event (identified in step (b)) as the event to revive
     236                 : via a call to SetEvent().
     237                 : 
     238                 : d) The Signal() method (step c) calls SetEvent() on waiting_event (step b).
     239                 : 
     240                 : e) The waiting cv_event (step b) is now signaled, but no thread is
     241                 : waiting on it.
     242                 : 
     243                 : f) When that waiting_event (step b) is reused, it will immediately
     244                 : be signaled (spuriously).
     245                 : 
     246                 : 
     247                 : 4) Why do you recycle events, and cause spurious signals?  First off,
     248                 : the spurious events are very rare.  They can only (I think) appear
     249                 : when the race described in FAQ-question-3 takes place.  This should be
     250                 : very rare.  Most(?)  uses will involve only timer expiration, or only
     251                 : Signal/Broadcast() actions.  When both are used, it will be rare that
     252                 : the race will appear, and it would require MANY Wait() and signaling
     253                 : activities.  If this implementation did not recycle events, then it
     254                 : would have to create and destroy events for every call to Wait().
     255                 : That allocation/deallocation and associated construction/destruction
     256                 : would be costly (per wait), and would only be a rare benefit (when the
     257                 : race was "lost" and a spurious signal took place). That would be bad
     258                 : (IMO) optimization trade-off.  Finally, such spurious events are
     259                 : allowed by the specification of condition variables (such as
     260                 : implemented in Vista), and hence it is better if any user accommodates
     261                 : such spurious events (see usage note in condition_variable.h).
     262                 : 
     263                 : 5) Why don't you reset events when you are about to recycle them, or
     264                 : about to reuse them, so that the spurious signals don't take place?
     265                 : The thread described in FAQ-question-3 step c may be pre-empted for an
     266                 : arbitrary length of time before proceeding to step d.  As a result,
     267                 : the wait_event may actually be re-used *before* step (e) is reached.
     268                 : As a result, calling reset would not help significantly.
     269                 : 
     270                 : 6) How is it that the callers lock is released atomically with the
     271                 : entry into a wait state?  We commit to the wait activity when we
     272                 : allocate the wait_event for use in a given call to Wait().  This
     273                 : allocation takes place before the caller's lock is released (and
     274                 : actually before our internal_lock_ is released).  That allocation is
     275                 : the defining moment when "the wait state has been entered," as that
     276                 : thread *can* now be signaled by a call to Broadcast() or Signal().
     277                 : Hence we actually "commit to wait" before releasing the lock, making
     278                 : the pair effectively atomic.
     279                 : 
     280                 : 8) Why do you need to lock your data structures during waiting, as the
     281                 : caller is already in possession of a lock?  We need to Acquire() and
     282                 : Release() our internal lock during Signal() and Broadcast().  If we tried
     283                 : to use a callers lock for this purpose, we might conflict with their
     284                 : external use of the lock.  For example, the caller may use to consistently
     285                 : hold a lock on one thread while calling Signal() on another, and that would
     286                 : block Signal().
     287                 : 
     288                 : 9) Couldn't a more efficient implementation be provided if you
     289                 : preclude using more than one external lock in conjunction with a
     290                 : single ConditionVariable instance?  Yes, at least it could be viewed
     291                 : as a simpler API (since you don't have to reiterate the lock argument
     292                 : in each Wait() call).  One of the constructors now takes a specific
     293                 : lock as an argument, and a there are corresponding Wait() calls that
     294                 : don't specify a lock now.  It turns that the resulting implmentation
     295                 : can't be made more efficient, as the internal lock needs to be used by
     296                 : Signal() and Broadcast(), to access internal data structures.  As a
     297                 : result, I was not able to utilize the user supplied lock (which is
     298                 : being used by the user elsewhere presumably) to protect the private
     299                 : member access.
     300                 : 
     301                 : 9) Since you have a second lock, how can be be sure that there is no
     302                 : possible deadlock scenario?  Our internal_lock_ is always the last
     303                 : lock acquired, and the first one released, and hence a deadlock (due
     304                 : to critical section problems) is impossible as a consequence of our
     305                 : lock.
     306                 : 
     307                 : 10) When doing a Broadcast(), why did you copy all the events into
     308                 : an STL queue, rather than making a linked-loop, and iterating over it?
     309                 : The iterating during Broadcast() is done so outside the protection
     310                 : of the internal lock. As a result, other threads, such as the thread
     311                 : wherein a related event is waiting, could asynchronously manipulate
     312                 : the links around a cv_event.  As a result, the link structure cannot
     313                 : be used outside a lock.  Broadcast() could iterate over waiting
     314                 : events by cycling in-and-out of the protection of the internal_lock,
     315                 : but that appears more expensive than copying the list into an STL
     316                 : stack.
     317                 : 
     318                 : 11) Why did the lock.h file need to be modified so much for this
     319                 : change?  Central to a Condition Variable is the atomic release of a
     320                 : lock during a Wait().  This places Wait() functionality exactly
     321                 : mid-way between the two classes, Lock and Condition Variable.  Given
     322                 : that there can be nested Acquire()'s of locks, and Wait() had to
     323                 : Release() completely a held lock, it was necessary to augment the Lock
     324                 : class with a recursion counter. Even more subtle is the fact that the
     325                 : recursion counter (in a Lock) must be protected, as many threads can
     326                 : access it asynchronously.  As a positive fallout of this, there are
     327                 : now some DCHECKS to be sure no one Release()s a Lock more than they
     328                 : Acquire()ed it, and there is ifdef'ed functionality that can detect
     329                 : nested locks (legal under windows, but not under Posix).
     330                 : 
     331                 : 12) Why is it that the cv_events removed from list in Broadcast() and Signal()
     332                 : are not leaked?  How are they recovered??  The cv_events that appear to leak are
     333                 : taken from the waiting_list_.  For each element in that list, there is currently
     334                 : a thread in or around the WaitForSingleObject() call of Wait(), and those
     335                 : threads have references to these otherwise leaked events. They are passed as
     336                 : arguments to be recycled just aftre returning from WaitForSingleObject().
     337                 : 
     338                 : 13) Why did you use a custom container class (the linked list), when STL has
     339                 : perfectly good containers, such as an STL list?  The STL list, as with any
     340                 : container, does not guarantee the utility of an iterator across manipulation
     341                 : (such as insertions and deletions) of the underlying container.  The custom
     342                 : double-linked-list container provided that assurance.  I don't believe any
     343                 : combination of STL containers provided the services that were needed at the same
     344                 : O(1) efficiency as the custom linked list.  The unusual requirement
     345                 : for the container class is that a reference to an item within a container (an
     346                 : iterator) needed to be maintained across an arbitrary manipulation of the
     347                 : container.  This requirement exposes itself in the Wait() method, where a
     348                 : waiting_event must be selected prior to the WaitForSingleObject(), and then it
     349                 : must be used as part of recycling to remove the related instance from the
     350                 : waiting_list.  A hash table (STL map) could be used, but I was embarrased to
     351                 : use a complex and relatively low efficiency container when a doubly linked list
     352                 : provided O(1) performance in all required operations.  Since other operations
     353                 : to provide performance-and/or-fairness required queue (FIFO) and list (LIFO)
     354                 : containers, I would also have needed to use an STL list/queue as well as an STL
     355                 : map.  In the end I decided it would be "fun" to just do it right, and I
     356                 : put so many assertions (DCHECKs) into the container class that it is trivial to
     357                 : code review and validate its correctness.
     358                 : 
     359                 : */

Generated by: LCOV version 1.7