Statically allocate futex array
[folly.git] / folly / detail / Futex.cpp
index 89d99722d9cb8ae1025a67aef71ae6f8778b7531..a7847b18b637b8cd8038a54255c8e98d1beb5d36 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 #include <folly/detail/Futex.h>
+#include <boost/intrusive/list.hpp>
+#include <folly/Indestructible.h>
+#include <folly/ScopeGuard.h>
+#include <folly/hash/Hash.h>
+#include <folly/portability/SysSyscall.h>
 #include <stdint.h>
 #include <string.h>
+#include <array>
+#include <cerrno>
 #include <condition_variable>
 #include <mutex>
-#include <boost/intrusive/list.hpp>
-#include <folly/Hash.h>
-#include <folly/ScopeGuard.h>
 
 #ifdef __linux__
-# include <errno.h>
-# include <linux/futex.h>
-# include <sys/syscall.h>
+#include <linux/futex.h>
 #endif
 
 using namespace std::chrono;
 
-namespace folly { namespace detail {
+namespace folly {
+namespace detail {
 
 namespace {
 
@@ -40,8 +43,24 @@ namespace {
 
 #ifdef __linux__
 
+/// Certain toolchains (like Android's) don't include the full futex API in
+/// their headers even though they support it. Make sure we have our constants
+/// even if the headers don't have them.
+#ifndef FUTEX_WAIT_BITSET
+# define FUTEX_WAIT_BITSET 9
+#endif
+#ifndef FUTEX_WAKE_BITSET
+# define FUTEX_WAKE_BITSET 10
+#endif
+#ifndef FUTEX_PRIVATE_FLAG
+# define FUTEX_PRIVATE_FLAG 128
+#endif
+#ifndef FUTEX_CLOCK_REALTIME
+# define FUTEX_CLOCK_REALTIME 256
+#endif
+
 int nativeFutexWake(void* addr, int count, uint32_t wakeMask) {
-  int rv = syscall(SYS_futex,
+  int rv = syscall(__NR_futex,
                    addr, /* addr1 */
                    FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, /* op */
                    count, /* val */
@@ -49,8 +68,13 @@ int nativeFutexWake(void* addr, int count, uint32_t wakeMask) {
                    nullptr, /* addr2 */
                    wakeMask); /* val3 */
 
-  assert(rv >= 0);
-
+  /* NOTE: we ignore errors on wake for the case of a futex
+     guarding its own destruction, similar to this
+     glibc bug with sem_post/sem_wait:
+     https://sourceware.org/bugzilla/show_bug.cgi?id=12674 */
+  if (rv < 0) {
+    return 0;
+  }
   return rv;
 }
 
@@ -58,13 +82,20 @@ template <class Clock>
 struct timespec
 timeSpecFromTimePoint(time_point<Clock> absTime)
 {
-  auto duration = absTime.time_since_epoch();
-  if (duration.count() < 0) {
+  auto epoch = absTime.time_since_epoch();
+  if (epoch.count() < 0) {
     // kernel timespec_valid requires non-negative seconds and nanos in [0,1G)
-    duration = Clock::duration::zero();
+    epoch = Clock::duration::zero();
   }
-  auto secs = duration_cast<seconds>(duration);
-  auto nanos = duration_cast<nanoseconds>(duration - secs);
+
+  // timespec-safe seconds and nanoseconds;
+  // chrono::{nano,}seconds are `long long int`
+  // whereas timespec uses smaller types
+  using time_t_seconds = duration<std::time_t, seconds::period>;
+  using long_nanos = duration<long int, nanoseconds::period>;
+
+  auto secs = duration_cast<time_t_seconds>(epoch);
+  auto nanos = duration_cast<long_nanos>(epoch - secs);
   struct timespec result = { secs.count(), nanos.count() };
   return result;
 }
@@ -91,7 +122,7 @@ FutexResult nativeFutexWaitImpl(void* addr,
 
   // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET requires an absolute timeout
   // value - http://locklessinc.com/articles/futex_cheat_sheet/
-  int rv = syscall(SYS_futex,
+  int rv = syscall(__NR_futex,
                    addr, /* addr1 */
                    op, /* op */
                    expected, /* val */
@@ -157,23 +188,20 @@ struct EmulatedFutexBucket {
   std::mutex mutex_;
   boost::intrusive::list<EmulatedFutexWaitNode> waiters_;
 
-  static const size_t kNumBuckets = 4096;
-  static EmulatedFutexBucket* gBuckets;
-  static std::once_flag gBucketInit;
+  static constexpr size_t const kNumBuckets = kIsMobile ? 256 : 4096;
 
   static EmulatedFutexBucket& bucketFor(void* addr) {
-    std::call_once(gBucketInit, [](){
-      gBuckets = new EmulatedFutexBucket[kNumBuckets];
-    });
-    uint64_t mixedBits = folly::hash::twang_mix64(
-        reinterpret_cast<uintptr_t>(addr));
-    return gBuckets[mixedBits % kNumBuckets];
+    // Statically allocating this lets us use this in allocation-sensitive
+    // contexts. This relies on the assumption that std::mutex won't dynamically
+    // allocate memory, which we assume to be the case on Linux and iOS.
+    static Indestructible<std::array<EmulatedFutexBucket, kNumBuckets>>
+        gBuckets;
+    uint64_t mixedBits =
+        folly::hash::twang_mix64(reinterpret_cast<uintptr_t>(addr));
+    return (*gBuckets)[mixedBits % kNumBuckets];
   }
 };
 
-EmulatedFutexBucket* EmulatedFutexBucket::gBuckets;
-std::once_flag EmulatedFutexBucket::gBucketInit;
-
 int emulatedFutexWake(void* addr, int count, uint32_t waitMask) {
   auto& bucket = EmulatedFutexBucket::bucketFor(addr);
   std::unique_lock<std::mutex> bucketLock(bucket.mutex_);
@@ -197,21 +225,25 @@ int emulatedFutexWake(void* addr, int count, uint32_t waitMask) {
   return numAwoken;
 }
 
+template <typename F>
 FutexResult emulatedFutexWaitImpl(
-        void* addr,
-        uint32_t expected,
-        time_point<system_clock>* absSystemTime,
-        time_point<steady_clock>* absSteadyTime,
-        uint32_t waitMask) {
+    F* futex,
+    uint32_t expected,
+    time_point<system_clock>* absSystemTime,
+    time_point<steady_clock>* absSteadyTime,
+    uint32_t waitMask) {
+  static_assert(
+      std::is_same<F, Futex<std::atomic>>::value ||
+          std::is_same<F, Futex<EmulatedFutexAtomic>>::value,
+      "Type F must be either Futex<std::atomic> or Futex<EmulatedFutexAtomic>");
+  void* addr = static_cast<void*>(futex);
   auto& bucket = EmulatedFutexBucket::bucketFor(addr);
   EmulatedFutexWaitNode node(addr, waitMask);
 
   {
     std::unique_lock<std::mutex> bucketLock(bucket.mutex_);
 
-    uint32_t actual;
-    memcpy(&actual, addr, sizeof(uint32_t));
-    if (actual != expected) {
+    if (futex->load(std::memory_order_relaxed) != expected) {
       return FutexResult::VALUE_CHANGED;
     }
 
@@ -243,8 +275,7 @@ FutexResult emulatedFutexWaitImpl(
   return FutexResult::AWOKEN;
 }
 
-} // anon namespace
-
+} // namespace
 
 /////////////////////////////////
 // Futex<> specializations
@@ -291,4 +322,5 @@ Futex<EmulatedFutexAtomic>::futexWaitImpl(
       this, expected, absSystemTime, absSteadyTime, waitMask);
 }
 
-}} // namespace folly::detail
+} // namespace detail
+} // namespace folly