Home | History | Annotate | Download | only in src
      1 // Copyright 2015 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "src/futex-emulation.h"
      6 
      7 #include <limits>
      8 
      9 #include "src/base/macros.h"
     10 #include "src/base/platform/time.h"
     11 #include "src/conversions.h"
     12 #include "src/handles-inl.h"
     13 #include "src/isolate.h"
     14 #include "src/objects-inl.h"
     15 #include "src/objects/js-array-buffer-inl.h"
     16 
     17 namespace v8 {
     18 namespace internal {
     19 
     20 using AtomicsWaitEvent = v8::Isolate::AtomicsWaitEvent;
     21 
     22 base::LazyMutex FutexEmulation::mutex_ = LAZY_MUTEX_INITIALIZER;
     23 base::LazyInstance<FutexWaitList>::type FutexEmulation::wait_list_ =
     24     LAZY_INSTANCE_INITIALIZER;
     25 
     26 
     27 void FutexWaitListNode::NotifyWake() {
     28   // Lock the FutexEmulation mutex before notifying. We know that the mutex
     29   // will have been unlocked if we are currently waiting on the condition
     30   // variable.
     31   //
     32   // The mutex may also not be locked if the other thread is currently handling
     33   // interrupts, or if FutexEmulation::Wait was just called and the mutex
     34   // hasn't been locked yet. In either of those cases, we set the interrupted
     35   // flag to true, which will be tested after the mutex is re-locked.
     36   base::LockGuard<base::Mutex> lock_guard(FutexEmulation::mutex_.Pointer());
     37   if (waiting_) {
     38     cond_.NotifyOne();
     39     interrupted_ = true;
     40   }
     41 }
     42 
     43 
     44 FutexWaitList::FutexWaitList() : head_(nullptr), tail_(nullptr) {}
     45 
     46 
     47 void FutexWaitList::AddNode(FutexWaitListNode* node) {
     48   DCHECK(node->prev_ == nullptr && node->next_ == nullptr);
     49   if (tail_) {
     50     tail_->next_ = node;
     51   } else {
     52     head_ = node;
     53   }
     54 
     55   node->prev_ = tail_;
     56   node->next_ = nullptr;
     57   tail_ = node;
     58 }
     59 
     60 
     61 void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
     62   if (node->prev_) {
     63     node->prev_->next_ = node->next_;
     64   } else {
     65     head_ = node->next_;
     66   }
     67 
     68   if (node->next_) {
     69     node->next_->prev_ = node->prev_;
     70   } else {
     71     tail_ = node->prev_;
     72   }
     73 
     74   node->prev_ = node->next_ = nullptr;
     75 }
     76 
     77 void AtomicsWaitWakeHandle::Wake() {
     78   // Adding a separate `NotifyWake()` variant that doesn't acquire the lock
     79   // itself would likely just add unnecessary complexity..
     80   // The split lock by itself isnt an issue, as long as the caller properly
     81   // synchronizes this with the closing `AtomicsWaitCallback`.
     82   {
     83     base::LockGuard<base::Mutex> lock_guard(FutexEmulation::mutex_.Pointer());
     84     stopped_ = true;
     85   }
     86   isolate_->futex_wait_list_node()->NotifyWake();
     87 }
     88 
     89 Object* FutexEmulation::Wait(Isolate* isolate,
     90                              Handle<JSArrayBuffer> array_buffer, size_t addr,
     91                              int32_t value, double rel_timeout_ms) {
     92   DCHECK(addr < NumberToSize(array_buffer->byte_length()));
     93 
     94   void* backing_store = array_buffer->backing_store();
     95   int32_t* p =
     96       reinterpret_cast<int32_t*>(static_cast<int8_t*>(backing_store) + addr);
     97 
     98   FutexWaitListNode* node = isolate->futex_wait_list_node();
     99   node->backing_store_ = backing_store;
    100   node->wait_addr_ = addr;
    101   node->waiting_ = true;
    102 
    103   bool use_timeout = rel_timeout_ms != V8_INFINITY;
    104 
    105   base::TimeDelta rel_timeout;
    106   if (use_timeout) {
    107     // Convert to nanoseconds.
    108     double rel_timeout_ns = rel_timeout_ms *
    109                             base::Time::kNanosecondsPerMicrosecond *
    110                             base::Time::kMicrosecondsPerMillisecond;
    111     if (rel_timeout_ns >
    112         static_cast<double>(std::numeric_limits<int64_t>::max())) {
    113       // 2**63 nanoseconds is 292 years. Let's just treat anything greater as
    114       // infinite.
    115       use_timeout = false;
    116     } else {
    117       rel_timeout = base::TimeDelta::FromNanoseconds(
    118           static_cast<int64_t>(rel_timeout_ns));
    119     }
    120   }
    121 
    122   AtomicsWaitWakeHandle stop_handle(isolate);
    123 
    124   isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
    125                                   addr, value, rel_timeout_ms, &stop_handle);
    126 
    127   if (isolate->has_scheduled_exception()) {
    128     node->waiting_ = false;
    129     return isolate->PromoteScheduledException();
    130   }
    131 
    132   Object* result;
    133   AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
    134 
    135   do {  // Not really a loop, just makes it easier to break out early.
    136     base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
    137     // Reset node->waiting_ = false when leaving this scope (but while
    138     // still holding the lock).
    139     ResetWaitingOnScopeExit reset_waiting(node);
    140 
    141     if (*p != value) {
    142       result = ReadOnlyRoots(isolate).not_equal();
    143       callback_result = AtomicsWaitEvent::kNotEqual;
    144       break;
    145     }
    146 
    147     base::TimeTicks timeout_time;
    148     base::TimeTicks current_time;
    149 
    150     if (use_timeout) {
    151       current_time = base::TimeTicks::Now();
    152       timeout_time = current_time + rel_timeout;
    153     }
    154 
    155     wait_list_.Pointer()->AddNode(node);
    156 
    157     while (true) {
    158       bool interrupted = node->interrupted_;
    159       node->interrupted_ = false;
    160 
    161       // Unlock the mutex here to prevent deadlock from lock ordering between
    162       // mutex_ and mutexes locked by HandleInterrupts.
    163       mutex_.Pointer()->Unlock();
    164 
    165       // Because the mutex is unlocked, we have to be careful about not dropping
    166       // an interrupt. The notification can happen in three different places:
    167       // 1) Before Wait is called: the notification will be dropped, but
    168       //    interrupted_ will be set to 1. This will be checked below.
    169       // 2) After interrupted has been checked here, but before mutex_ is
    170       //    acquired: interrupted is checked again below, with mutex_ locked.
    171       //    Because the wakeup signal also acquires mutex_, we know it will not
    172       //    be able to notify until mutex_ is released below, when waiting on
    173       //    the condition variable.
    174       // 3) After the mutex is released in the call to WaitFor(): this
    175       // notification will wake up the condition variable. node->waiting() will
    176       // be false, so we'll loop and then check interrupts.
    177       if (interrupted) {
    178         Object* interrupt_object = isolate->stack_guard()->HandleInterrupts();
    179         if (interrupt_object->IsException(isolate)) {
    180           result = interrupt_object;
    181           callback_result = AtomicsWaitEvent::kTerminatedExecution;
    182           mutex_.Pointer()->Lock();
    183           break;
    184         }
    185       }
    186 
    187       mutex_.Pointer()->Lock();
    188 
    189       if (node->interrupted_) {
    190         // An interrupt occurred while the mutex_ was unlocked. Don't wait yet.
    191         continue;
    192       }
    193 
    194       if (stop_handle.has_stopped()) {
    195         node->waiting_ = false;
    196         callback_result = AtomicsWaitEvent::kAPIStopped;
    197       }
    198 
    199       if (!node->waiting_) {
    200         result = ReadOnlyRoots(isolate).ok();
    201         break;
    202       }
    203 
    204       // No interrupts, now wait.
    205       if (use_timeout) {
    206         current_time = base::TimeTicks::Now();
    207         if (current_time >= timeout_time) {
    208           result = ReadOnlyRoots(isolate).timed_out();
    209           callback_result = AtomicsWaitEvent::kTimedOut;
    210           break;
    211         }
    212 
    213         base::TimeDelta time_until_timeout = timeout_time - current_time;
    214         DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
    215         bool wait_for_result =
    216             node->cond_.WaitFor(mutex_.Pointer(), time_until_timeout);
    217         USE(wait_for_result);
    218       } else {
    219         node->cond_.Wait(mutex_.Pointer());
    220       }
    221 
    222       // Spurious wakeup, interrupt or timeout.
    223     }
    224 
    225     wait_list_.Pointer()->RemoveNode(node);
    226   } while (0);
    227 
    228   isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
    229                                   rel_timeout_ms, nullptr);
    230 
    231   if (isolate->has_scheduled_exception()) {
    232     CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
    233     result = isolate->PromoteScheduledException();
    234   }
    235 
    236   return result;
    237 }
    238 
    239 Object* FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
    240                              uint32_t num_waiters_to_wake) {
    241   DCHECK(addr < NumberToSize(array_buffer->byte_length()));
    242 
    243   int waiters_woken = 0;
    244   void* backing_store = array_buffer->backing_store();
    245 
    246   base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
    247   FutexWaitListNode* node = wait_list_.Pointer()->head_;
    248   while (node && num_waiters_to_wake > 0) {
    249     if (backing_store == node->backing_store_ && addr == node->wait_addr_) {
    250       node->waiting_ = false;
    251       node->cond_.NotifyOne();
    252       if (num_waiters_to_wake != kWakeAll) {
    253         --num_waiters_to_wake;
    254       }
    255       waiters_woken++;
    256     }
    257 
    258     node = node->next_;
    259   }
    260 
    261   return Smi::FromInt(waiters_woken);
    262 }
    263 
    264 Object* FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
    265                                              size_t addr) {
    266   DCHECK(addr < NumberToSize(array_buffer->byte_length()));
    267   void* backing_store = array_buffer->backing_store();
    268 
    269   base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
    270 
    271   int waiters = 0;
    272   FutexWaitListNode* node = wait_list_.Pointer()->head_;
    273   while (node) {
    274     if (backing_store == node->backing_store_ && addr == node->wait_addr_ &&
    275         node->waiting_) {
    276       waiters++;
    277     }
    278 
    279     node = node->next_;
    280   }
    281 
    282   return Smi::FromInt(waiters);
    283 }
    284 
    285 }  // namespace internal
    286 }  // namespace v8
    287