summaryrefslogtreecommitdiffstats
path: root/js/src/threading/windows/ConditionVariable.cpp
blob: 868c351416be5f4ab480e98ab68b00a41609cc55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/Assertions.h"

#include <float.h>
#include <intrin.h>
#include <stdlib.h>
#include <windows.h>

#include "threading/ConditionVariable.h"
#include "threading/Mutex.h"
#include "threading/windows/MutexPlatformData.h"

// Some versions of the Windows SDK have a bug where some interlocked functions
// are not redefined as compiler intrinsics. Fix that for the interlocked
// functions that are used in this file.
#if defined(_MSC_VER) && !defined(InterlockedExchangeAdd)
#define InterlockedExchangeAdd(addend, value)                                  \
  _InterlockedExchangeAdd((volatile long*)(addend), (long)(value))
#endif

#if defined(_MSC_VER) && !defined(InterlockedIncrement)
#define InterlockedIncrement(addend)                                           \
  _InterlockedIncrement((volatile long*)(addend))
#endif

// Windows XP and Server 2003 don't support condition variables natively. The
// NativeImports class is responsible for detecting native support and
// retrieving the appropriate function pointers. It gets instantiated once,
// using a static initializer.
class ConditionVariableNativeImports
{
public:
  ConditionVariableNativeImports() {
    HMODULE kernel32_dll = GetModuleHandle("kernel32.dll");
    MOZ_RELEASE_ASSERT(kernel32_dll != NULL);

#define LOAD_SYMBOL(symbol) loadSymbol(kernel32_dll, #symbol, symbol)
    supported_ = LOAD_SYMBOL(InitializeConditionVariable) &&
                 LOAD_SYMBOL(WakeConditionVariable) &&
                 LOAD_SYMBOL(WakeAllConditionVariable) &&
                 LOAD_SYMBOL(SleepConditionVariableCS);
#undef LOAD_SYMBOL
  }

  inline bool supported() const {
    return supported_;
  }

  void(WINAPI* InitializeConditionVariable)(CONDITION_VARIABLE* ConditionVariable);
  void(WINAPI* WakeAllConditionVariable)(PCONDITION_VARIABLE ConditionVariable);
  void(WINAPI* WakeConditionVariable)(CONDITION_VARIABLE* ConditionVariable);
  BOOL(WINAPI* SleepConditionVariableCS)(CONDITION_VARIABLE* ConditionVariable,
                                         CRITICAL_SECTION* CriticalSection,
                                         DWORD dwMilliseconds);

private:
  template <typename T>
  inline bool loadSymbol(HMODULE module, const char* name, T& fn) {
    FARPROC ptr = GetProcAddress(module, name);
    if (!ptr)
      return false;

    fn = reinterpret_cast<T>(ptr);
    return true;
  }

  bool supported_;
};

static ConditionVariableNativeImports sNativeImports;

// Wrapper for native condition variable APIs.
struct ConditionVariableNative
{
  inline void initialize() {
    sNativeImports.InitializeConditionVariable(&cv_);
  }

  inline void destroy() {
    // Native condition variables don't require cleanup.
  }

  inline void notify_one() { sNativeImports.WakeConditionVariable(&cv_); }

  inline void notify_all() { sNativeImports.WakeAllConditionVariable(&cv_); }

  inline bool wait(CRITICAL_SECTION* cs, DWORD msec) {
    return sNativeImports.SleepConditionVariableCS(&cv_, cs, msec);
  }

private:
  CONDITION_VARIABLE cv_;
};

// Fallback condition variable support for Windows XP and Server 2003. Given the
// difficulty of testing on these antiquated platforms and their rapidly
// diminishing market share, this implementation trades performance for
// predictable behavior.
struct ConditionVariableFallback
{
  static const uint32_t WAKEUP_MODE_NONE = 0;
  static const uint32_t WAKEUP_MODE_ONE = 0x40000000;
  static const uint32_t WAKEUP_MODE_ALL = 0x80000000;

  static const uint32_t WAKEUP_MODE_MASK = WAKEUP_MODE_ONE | WAKEUP_MODE_ALL;
  static const uint32_t SLEEPERS_COUNT_MASK = ~WAKEUP_MODE_MASK;

  void initialize()
  {
    // Initialize the state variable to 0 sleepers, no wakeup.
    sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;

    // Create a semaphore that prevents threads from entering sleep,
    // or waking other threads while a wakeup is ongoing.
    sleepWakeupSemaphore_ = CreateSemaphoreW(NULL, 1, 1, NULL);
    MOZ_RELEASE_ASSERT(sleepWakeupSemaphore_);

    // Use an auto-reset event for waking up a single sleeper.
    wakeOneEvent_ = CreateEventW(NULL, FALSE, FALSE, NULL);
    MOZ_RELEASE_ASSERT(wakeOneEvent_);

    // Use a manual-reset event for waking up all sleepers.
    wakeAllEvent_ = CreateEventW(NULL, TRUE, FALSE, NULL);
    MOZ_RELEASE_ASSERT(wakeAllEvent_);
  }

  void destroy()
  {
    BOOL r;

    MOZ_RELEASE_ASSERT(sleepersCountAndWakeupMode_ == (0 | WAKEUP_MODE_NONE));

    r = CloseHandle(sleepWakeupSemaphore_);
    MOZ_RELEASE_ASSERT(r);

    r = CloseHandle(wakeOneEvent_);
    MOZ_RELEASE_ASSERT(r);

    r = CloseHandle(wakeAllEvent_);
    MOZ_RELEASE_ASSERT(r);
  }

private:
  void wakeup(uint32_t wakeupMode, HANDLE wakeEvent)
  {
    // Ensure that only one thread at a time can wake up others.
    BOOL result = WaitForSingleObject(sleepWakeupSemaphore_, INFINITE);
    MOZ_RELEASE_ASSERT(result == WAIT_OBJECT_0);

    // Atomically set the wakeup mode and retrieve the number of sleepers.
    uint32_t wcwm = InterlockedExchangeAdd(&sleepersCountAndWakeupMode_,
                                           wakeupMode);
    uint32_t sleepersCount = wcwm & SLEEPERS_COUNT_MASK;
    MOZ_RELEASE_ASSERT((wcwm & WAKEUP_MODE_MASK) == WAKEUP_MODE_NONE);

    if (sleepersCount > 0) {
      // If there are any sleepers, set the wake event. The (last) woken
      // up thread is responsible for releasing the semaphore.
      BOOL success = SetEvent(wakeEvent);
      MOZ_RELEASE_ASSERT(success);

    } else {
      // If there are no sleepers, set the wakeup mode back to 'none'
      // and release the semaphore ourselves.
      sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;

      BOOL success = ReleaseSemaphore(sleepWakeupSemaphore_, 1, NULL);
      MOZ_RELEASE_ASSERT(success);
    }
  }

public:
  void notify_one() { wakeup(WAKEUP_MODE_ONE, wakeOneEvent_); }

  void notify_all() { wakeup(WAKEUP_MODE_ALL, wakeAllEvent_); }

  bool wait(CRITICAL_SECTION* userLock, DWORD msec)
  {
    // Make sure that we can't enter sleep when there are other threads
    // that still need to wake up on either of the wake events being set.
    DWORD result = WaitForSingleObject(sleepWakeupSemaphore_, INFINITE);
    MOZ_RELEASE_ASSERT(result == WAIT_OBJECT_0);

    // Register ourselves as a sleeper. Use an atomic operation, because
    // if another thread times out at the same time, it will decrement the
    // sleepers count without acquiring the semaphore.
    uint32_t wcwm = InterlockedIncrement(&sleepersCountAndWakeupMode_);
    MOZ_RELEASE_ASSERT((wcwm & WAKEUP_MODE_MASK) == WAKEUP_MODE_NONE);

    // Now that that this thread has been enlisted as a sleeper, it is safe
    // again for other threads to do a wakeup.
    BOOL success = ReleaseSemaphore(sleepWakeupSemaphore_, 1, NULL);
    MOZ_RELEASE_ASSERT(success);

    // Release the caller's mutex.
    LeaveCriticalSection(userLock);

    // Wait for either event to become signaled, which happens when
    // notify_one() or notify_all() is called, or for a timeout.
    HANDLE handles[2] = { wakeOneEvent_, wakeAllEvent_ };
    DWORD waitResult = WaitForMultipleObjects(2, handles, FALSE, msec);
    MOZ_RELEASE_ASSERT(waitResult == WAIT_OBJECT_0 ||
                       waitResult == WAIT_OBJECT_0 + 1 ||
                       (waitResult == WAIT_TIMEOUT && msec != INFINITE));

    // Atomically decrease the sleepers count and retrieve the wakeup mode
    // and new sleepers count.
    // If the wait returned because wakeOneEvent_ was set, we are certain
    // that the wakeup mode will be WAKEUP_MODE_ONE. In that case,
    // atomically reset the wakeup mode to 'none', because if another
    // thread's sleep times out at same time and it finds that it was the
    // last sleeper, it decides whether or not to reset the wakeOneEvent_
    // based on the current wakeup mode.
    uint32_t sub;
    if (waitResult == WAIT_OBJECT_0)
      sub = 1 | WAKEUP_MODE_ONE;
    else
      sub = 1;
    // Note that InterlockedExchangeAdd returns the old value, but it's
    // easier to work with the new value.
    wcwm = InterlockedExchangeAdd(&sleepersCountAndWakeupMode_, -sub) - sub;

    uint32_t wakeupMode = wcwm & WAKEUP_MODE_MASK;
    uint32_t sleepersCount = wcwm & SLEEPERS_COUNT_MASK;

    bool releaseSleepWakeupSemaphore = false;

    if (waitResult == WAIT_OBJECT_0) {
      // The wake-one event is an auto-reset event so if we're woken by
      // it, it should already have been reset. We also already removed
      // the WAKEUP_MODE_ONE bit so the wakeup mode should now be 'none'
      // again.
      MOZ_RELEASE_ASSERT(wakeupMode == WAKEUP_MODE_NONE);

      // The signaling thread has acquired the enter-wakeup semaphore and
      // expects the woken (this) thread to release it again.
      releaseSleepWakeupSemaphore = true;

    } else if (waitResult == WAIT_TIMEOUT && wakeupMode == WAKEUP_MODE_ONE &&
               sleepersCount == 0) {
      // In theory a race condition is possible where the last sleeper
      // times out right at the moment that another thread signals it.
      // If that just happened we now have a dangling signal event and
      // mode, but no threads to be woken up by it, and we need to clean
      // that up.
      BOOL success = ResetEvent(wakeOneEvent_);
      MOZ_RELEASE_ASSERT(success);

      // This is safe - we are certain there are no other sleepers that
      // could wake up right now, and the semaphore ensures that no
      // non-sleeping threads are messing with
      // sleepersCountAndWakeupMode_.
      sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;

      // The signaling thread has acquired the sleep-wakeup semaphore and
      // expects the woken thread to release it. But since there are no
      // sleeping threads left this thread will do it instead.
      releaseSleepWakeupSemaphore = true;

    } else if (wakeupMode == WAKEUP_MODE_ALL && sleepersCount == 0) {
      // If this was the last thread waking up in response to a
      // notify_all, clear the wakeup mode and reset the wake-all event.
      // A race condition similar to the case described above could
      // occur, so waitResult could be WAIT_TIMEOUT, but that doesn't
      // matter for the actions that need to be taken.
      MOZ_RELEASE_ASSERT(waitResult = WAIT_OBJECT_0 + 1 ||
                         waitResult == WAIT_TIMEOUT);

      BOOL success = ResetEvent(wakeAllEvent_);
      MOZ_RELEASE_ASSERT(success);

      sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;

      // The broadcasting thread has acquired the enter-wakeup semaphore
      // and expects the last thread that wakes up to release it.
      releaseSleepWakeupSemaphore = true;

    } else if ((waitResult == WAIT_TIMEOUT && msec != INFINITE) ||
               (waitResult == WAIT_OBJECT_0 + 1 &&
                wakeupMode == WAKEUP_MODE_ALL)) {
      // Either:
      //   * The wait timed out but found no active notify_one or notify_all
      //     the moment it decreased the wait count.
      //   * A notify_all woke up this thread but there are more threads
      //     that need to be woken up by the wake-all event.
      // These are ordinary conditions in which we don't have to do
      // anything.

    } else {
      MOZ_CRASH("invalid wakeup condition");
    }

    // Release the enter-wakeup semaphore if the wakeup condition requires
    // us to do it.
    if (releaseSleepWakeupSemaphore) {
      BOOL success = ReleaseSemaphore(sleepWakeupSemaphore_, 1, NULL);
      MOZ_RELEASE_ASSERT(success);
    }

    // Reacquire the user mutex.
    EnterCriticalSection(userLock);

    // Return true if woken up, false when timed out.
    if (waitResult == WAIT_TIMEOUT) {
      SetLastError(ERROR_TIMEOUT);
      return false;
    }
    return true;
  }

private:
  uint32_t sleepersCountAndWakeupMode_;
  HANDLE sleepWakeupSemaphore_;
  HANDLE wakeOneEvent_;
  HANDLE wakeAllEvent_;
};

struct js::ConditionVariable::PlatformData
{
  union
  {
    ConditionVariableNative native;
    ConditionVariableFallback fallback;
  };
};

js::ConditionVariable::ConditionVariable()
{
  if (sNativeImports.supported())
    platformData()->native.initialize();
  else
    platformData()->fallback.initialize();
}

void
js::ConditionVariable::notify_one()
{
  if (sNativeImports.supported())
    platformData()->native.notify_one();
  else
    platformData()->fallback.notify_one();
}

void
js::ConditionVariable::notify_all()
{
  if (sNativeImports.supported())
    platformData()->native.notify_all();
  else
    platformData()->fallback.notify_all();
}

void
js::ConditionVariable::wait(UniqueLock<Mutex>& lock)
{
  CRITICAL_SECTION* cs = &lock.lock.platformData()->criticalSection;
  bool r;
  if (sNativeImports.supported())
    r = platformData()->native.wait(cs, INFINITE);
  else
    r = platformData()->fallback.wait(cs, INFINITE);
  MOZ_RELEASE_ASSERT(r);
}

js::CVStatus
js::ConditionVariable::wait_until(UniqueLock<Mutex>& lock,
                                  const mozilla::TimeStamp& abs_time)
{
  return wait_for(lock, abs_time - mozilla::TimeStamp::Now());
}

js::CVStatus
js::ConditionVariable::wait_for(UniqueLock<Mutex>& lock,
                                const mozilla::TimeDuration& rel_time)
{
  CRITICAL_SECTION* cs = &lock.lock.platformData()->criticalSection;

  // Note that DWORD is unsigned, so we have to be careful to clamp at 0.
  // If rel_time is Forever, then ToMilliseconds is +inf, which evaluates as
  // greater than UINT32_MAX, resulting in the correct INFINITE wait.
  double msecd = rel_time.ToMilliseconds();
  DWORD msec = msecd < 0.0
               ? 0
               : msecd > UINT32_MAX
                 ? INFINITE
                 : static_cast<DWORD>(msecd);

  BOOL r;
  if (sNativeImports.supported())
    r = platformData()->native.wait(cs, msec);
  else
    r = platformData()->fallback.wait(cs, msec);
  if (r)
    return CVStatus::NoTimeout;
  MOZ_RELEASE_ASSERT(GetLastError() == ERROR_TIMEOUT);
  return CVStatus::Timeout;
}

js::ConditionVariable::~ConditionVariable()
{
  if (sNativeImports.supported())
    platformData()->native.destroy();
  else
    platformData()->fallback.destroy();
}

inline js::ConditionVariable::PlatformData*
js::ConditionVariable::platformData()
{
  static_assert(sizeof platformData_ >= sizeof(PlatformData),
                "platformData_ is too small");
  return reinterpret_cast<PlatformData*>(platformData_);
}