1// Copyright 2019 The Marl Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef marl_event_h
16#define marl_event_h
17
18#include "conditionvariable.h"
19#include "containers.h"
20#include "export.h"
21#include "memory.h"
22
23#include <chrono>
24
25namespace marl {
26
27// Event is a synchronization primitive used to block until a signal is raised.
28class Event {
29 public:
30 enum class Mode : uint8_t {
31 // The event signal will be automatically reset when a call to wait()
32 // returns.
33 // A single call to signal() will only unblock a single (possibly
34 // future) call to wait().
35 Auto,
36
37 // While the event is in the signaled state, any calls to wait() will
38 // unblock without automatically reseting the signaled state.
39 // The signaled state can be reset with a call to clear().
40 Manual
41 };
42
43 MARL_NO_EXPORT inline Event(Mode mode = Mode::Auto,
44 bool initialState = false,
45 Allocator* allocator = Allocator::Default);
46
47 // signal() signals the event, possibly unblocking a call to wait().
48 MARL_NO_EXPORT inline void signal() const;
49
50 // clear() clears the signaled state.
51 MARL_NO_EXPORT inline void clear() const;
52
53 // wait() blocks until the event is signaled.
54 // If the event was constructed with the Auto Mode, then only one
55 // call to wait() will unblock before returning, upon which the signalled
56 // state will be automatically cleared.
57 MARL_NO_EXPORT inline void wait() const;
58
59 // wait_for() blocks until the event is signaled, or the timeout has been
60 // reached.
61 // If the timeout was reached, then wait_for() return false.
62 // If the event is signalled and event was constructed with the Auto Mode,
63 // then only one call to wait() will unblock before returning, upon which the
64 // signalled state will be automatically cleared.
65 template <typename Rep, typename Period>
66 MARL_NO_EXPORT inline bool wait_for(
67 const std::chrono::duration<Rep, Period>& duration) const;
68
69 // wait_until() blocks until the event is signaled, or the timeout has been
70 // reached.
71 // If the timeout was reached, then wait_for() return false.
72 // If the event is signalled and event was constructed with the Auto Mode,
73 // then only one call to wait() will unblock before returning, upon which the
74 // signalled state will be automatically cleared.
75 template <typename Clock, typename Duration>
76 MARL_NO_EXPORT inline bool wait_until(
77 const std::chrono::time_point<Clock, Duration>& timeout) const;
78
79 // test() returns true if the event is signaled, otherwise false.
80 // If the event is signalled and was constructed with the Auto Mode
81 // then the signalled state will be automatically cleared upon returning.
82 MARL_NO_EXPORT inline bool test() const;
83
84 // isSignalled() returns true if the event is signaled, otherwise false.
85 // Unlike test() the signal is not automatically cleared when the event was
86 // constructed with the Auto Mode.
87 // Note: No lock is held after bool() returns, so the event state may
88 // immediately change after returning. Use with caution.
89 MARL_NO_EXPORT inline bool isSignalled() const;
90
91 // any returns an event that is automatically signalled whenever any of the
92 // events in the list are signalled.
93 template <typename Iterator>
94 MARL_NO_EXPORT inline static Event any(Mode mode,
95 const Iterator& begin,
96 const Iterator& end);
97
98 // any returns an event that is automatically signalled whenever any of the
99 // events in the list are signalled.
100 // This overload defaults to using the Auto mode.
101 template <typename Iterator>
102 MARL_NO_EXPORT inline static Event any(const Iterator& begin,
103 const Iterator& end);
104
105 private:
106 struct Shared {
107 MARL_NO_EXPORT inline Shared(Allocator* allocator,
108 Mode mode,
109 bool initialState);
110 MARL_NO_EXPORT inline void signal();
111 MARL_NO_EXPORT inline void wait();
112
113 template <typename Rep, typename Period>
114 MARL_NO_EXPORT inline bool wait_for(
115 const std::chrono::duration<Rep, Period>& duration);
116
117 template <typename Clock, typename Duration>
118 MARL_NO_EXPORT inline bool wait_until(
119 const std::chrono::time_point<Clock, Duration>& timeout);
120
121 marl::mutex mutex;
122 ConditionVariable cv;
123 containers::vector<std::shared_ptr<Shared>, 1> deps;
124 const Mode mode;
125 bool signalled;
126 };
127
128 const std::shared_ptr<Shared> shared;
129};
130
131Event::Shared::Shared(Allocator* allocator, Mode mode, bool initialState)
132 : cv(allocator), mode(mode), signalled(initialState) {}
133
134void Event::Shared::signal() {
135 marl::lock lock(mutex);
136 if (signalled) {
137 return;
138 }
139 signalled = true;
140 if (mode == Mode::Auto) {
141 cv.notify_one();
142 } else {
143 cv.notify_all();
144 }
145 for (auto dep : deps) {
146 dep->signal();
147 }
148}
149
150void Event::Shared::wait() {
151 marl::lock lock(mutex);
152 cv.wait(lock, [&] { return signalled; });
153 if (mode == Mode::Auto) {
154 signalled = false;
155 }
156}
157
158template <typename Rep, typename Period>
159bool Event::Shared::wait_for(
160 const std::chrono::duration<Rep, Period>& duration) {
161 marl::lock lock(mutex);
162 if (!cv.wait_for(lock, duration, [&] { return signalled; })) {
163 return false;
164 }
165 if (mode == Mode::Auto) {
166 signalled = false;
167 }
168 return true;
169}
170
171template <typename Clock, typename Duration>
172bool Event::Shared::wait_until(
173 const std::chrono::time_point<Clock, Duration>& timeout) {
174 marl::lock lock(mutex);
175 if (!cv.wait_until(lock, timeout, [&] { return signalled; })) {
176 return false;
177 }
178 if (mode == Mode::Auto) {
179 signalled = false;
180 }
181 return true;
182}
183
184Event::Event(Mode mode /* = Mode::Auto */,
185 bool initialState /* = false */,
186 Allocator* allocator /* = Allocator::Default */)
187 : shared(allocator->make_shared<Shared>(allocator, mode, initialState)) {}
188
189void Event::signal() const {
190 shared->signal();
191}
192
193void Event::clear() const {
194 marl::lock lock(shared->mutex);
195 shared->signalled = false;
196}
197
198void Event::wait() const {
199 shared->wait();
200}
201
202template <typename Rep, typename Period>
203bool Event::wait_for(const std::chrono::duration<Rep, Period>& duration) const {
204 return shared->wait_for(duration);
205}
206
207template <typename Clock, typename Duration>
208bool Event::wait_until(
209 const std::chrono::time_point<Clock, Duration>& timeout) const {
210 return shared->wait_until(timeout);
211}
212
213bool Event::test() const {
214 marl::lock lock(shared->mutex);
215 if (!shared->signalled) {
216 return false;
217 }
218 if (shared->mode == Mode::Auto) {
219 shared->signalled = false;
220 }
221 return true;
222}
223
224bool Event::isSignalled() const {
225 marl::lock lock(shared->mutex);
226 return shared->signalled;
227}
228
229template <typename Iterator>
230Event Event::any(Mode mode, const Iterator& begin, const Iterator& end) {
231 Event any(mode, false);
232 for (auto it = begin; it != end; it++) {
233 auto s = it->shared;
234 marl::lock lock(s->mutex);
235 if (s->signalled) {
236 any.signal();
237 }
238 s->deps.push_back(any.shared);
239 }
240 return any;
241}
242
243template <typename Iterator>
244Event Event::any(const Iterator& begin, const Iterator& end) {
245 return any(Mode::Auto, begin, end);
246}
247
248} // namespace marl
249
250#endif // marl_event_h
251