/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 |