Coverage Report

Created: 2025-06-24 06:43

/src/hermes/lib/VM/TimeLimitMonitor.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#include "hermes/VM/TimeLimitMonitor.h"
9
10
#include "hermes/Support/OSCompat.h"
11
#include "hermes/VM/Runtime.h"
12
13
#include <algorithm>
14
15
namespace hermes {
16
namespace vm {
17
18
53
std::shared_ptr<TimeLimitMonitor> TimeLimitMonitor::getOrCreate() {
19
  /// The singleton payload, intentionally leaked to avoid destruction order
20
  /// fiasco.
21
53
  static struct Singleton {
22
    /// Synchronizes access to weakMonitor.
23
53
    std::mutex guard;
24
25
    /// Holds a weak reference to the current TimeLimitMonitor. This ensure that
26
    /// the monitor is destroyed when there are no more shared pointers to it,
27
    /// while still allowing this method to re-use the same singleton for as
28
    /// long as possible.
29
53
    std::weak_ptr<TimeLimitMonitor> weakMonitor;
30
53
  } *singleton = new Singleton();
31
32
53
  std::lock_guard<std::mutex> singletonGuard{singleton->guard};
33
53
  if (auto monitor = singleton->weakMonitor.lock()) {
34
0
    return monitor;
35
0
  }
36
37
  // Create the new monitor instance while saving a reference in the
38
  // singleton's weakMonitor member.
39
53
  auto monitor = std::make_shared<TimeLimitMonitor>();
40
53
  singleton->weakMonitor = monitor;
41
53
  return monitor;
42
53
}
43
44
53
TimeLimitMonitor::TimeLimitMonitor() {
45
  // Spawns a new thread that performs the time limit monitoring. This thread
46
  // is joined in the destructor.
47
53
  timerThread_ = std::thread(&TimeLimitMonitor::timerLoop, this);
48
53
}
49
50
53
TimeLimitMonitor::~TimeLimitMonitor() {
51
53
  {
52
53
    std::unique_lock<std::mutex> lockGuard(lock_);
53
    // Flipping enabled_ to false so the timeLoop thread will stop iterating and
54
    // allow the thread to complete.
55
53
    enabled_ = false;
56
53
  }
57
58
  // Tickle the timeLoop thread so it can terminate.
59
53
  timerLoopCond_.notify_all();
60
61
  /// And wait for the helper thread termination.
62
53
  timerThread_.join();
63
53
}
64
65
void TimeLimitMonitor::watchRuntime(
66
    Runtime &runtime,
67
53
    std::chrono::milliseconds timeout) {
68
53
  {
69
53
    std::lock_guard<std::mutex> lockGuard(lock_);
70
    // The execution deadline for runtime.
71
53
    auto runtimeDeadline = std::chrono::steady_clock::now() + timeout;
72
73
    // Set runtime's deadline to runtimeDeadline. This overwrites the current
74
    // deadline (if any).
75
53
    watchedRuntimes_[&runtime] = runtimeDeadline;
76
53
  }
77
78
  // Notify the timer loop so it updates the wait timeout in case
79
  // runtimeDeadline is earlier than the current earliest deadline being
80
  // tracked.
81
53
  timerLoopCond_.notify_all();
82
53
}
83
84
53
void TimeLimitMonitor::unwatchRuntime(Runtime &runtime) {
85
53
  std::lock_guard<std::mutex> lockGuard(lock_);
86
53
  watchedRuntimes_.erase(&runtime);
87
88
  // No need to notify the timer loop, even if runtime had the current earliest
89
  // deadline being watched -- in which case the timerLoop will run once,
90
  // figure out no runtimes timed out, and sleep again.
91
53
}
92
93
53
void TimeLimitMonitor::timerLoop() {
94
53
  oscompat::set_thread_name("time-limit-monitor");
95
96
53
  std::unique_lock<std::mutex> lockGuard(lock_);
97
98
  // Indicates that there currently is no deadline being "watched" for, so the
99
  // timerLoop should sleep until there's work to be done.
100
53
  static constexpr Deadline NoDeadline = Deadline::max();
101
102
106
  while (enabled_) {
103
    // Assume there's no deadline in watchedRuntimes_ -- i.e., no Runtimes are
104
    // being watched.
105
53
    Deadline nextDeadline = NoDeadline;
106
53
    Deadline now = std::chrono::steady_clock::now();
107
108
53
    for (auto it = watchedRuntimes_.begin(), end = watchedRuntimes_.end();
109
106
         it != end;) {
110
53
      auto curr = it++;
111
112
53
      if (curr->second <= now) {
113
        // curr's deadline has elapsed; notify the VM.
114
0
        curr->first->triggerTimeoutAsyncBreak();
115
116
        // We're done watching the VM, so remove curr from watchedRuntimes_.
117
0
        watchedRuntimes_.erase(curr);
118
53
      } else {
119
        // curr's deadline is in the future, but it could be ealier than the
120
        // nextDeadline; Update the latter accordingly.
121
53
        nextDeadline = std::min(nextDeadline, curr->second);
122
53
      }
123
53
    }
124
125
53
    if (nextDeadline != NoDeadline) {
126
      // Sleep until the next deadline is reached, or the notification comes in
127
      // (e.g., time limit monitoring has been disabled and the thread needs to
128
      // terminate). Note that spurious wake ups are OK -- it just means that no
129
      // timeouts will have passed, and the thread will go back to waiting.
130
53
      timerLoopCond_.wait_until(lockGuard, nextDeadline);
131
53
    } else {
132
      // Work around overflow issues in some libstdcxx implementations by using
133
      // wait() when there's no active deadline. The timerLoop will be notified
134
      // by the VM thread when a new Runtime is registered or destroyed.
135
0
      timerLoopCond_.wait(lockGuard);
136
0
    }
137
53
  }
138
53
}
139
140
} // namespace vm
141
} // namespace hermes