barretenberg
Loading...
Searching...
No Matches
lightweightsemaphore.h
1// Provides an efficient implementation of a semaphore (LightweightSemaphore).
2// This is an extension of Jeff Preshing's sempahore implementation (licensed
3// under the terms of its separate zlib license) that has been adapted and
4// extended by Cameron Desrochers.
5
6#pragma once
7
8#include <cstddef> // For std::size_t
9#include <atomic>
10#include <type_traits> // For std::make_signed<T>
11
12#if defined(_WIN32)
13// Avoid including windows.h in a header; we only need a handful of
14// items, so we'll redeclare them here (this is relatively safe since
15// the API generally has to remain stable between Windows versions).
16// I know this is an ugly hack but it still beats polluting the global
17// namespace with thousands of generic names or adding a .cpp for nothing.
18extern "C" {
19struct _SECURITY_ATTRIBUTES;
20__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes,
21 long lInitialCount,
22 long lMaximumCount,
23 const wchar_t* lpName);
24__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
25__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
26__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
27}
28#elif defined(__MACH__)
29#include <mach/mach.h>
30#elif defined(__unix__) || defined(__wasm__)
31#include <semaphore.h>
32
33#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE)
34#if __GLIBC_PREREQ(2, 30)
35#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
36#endif
37#endif
38#endif
39
40namespace moodycamel {
41namespace details {
42
43// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
44// portable + lightweight semaphore implementations, originally from
45// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
46// LICENSE:
47// Copyright (c) 2015 Jeff Preshing
48//
49// This software is provided 'as-is', without any express or implied
50// warranty. In no event will the authors be held liable for any damages
51// arising from the use of this software.
52//
53// Permission is granted to anyone to use this software for any purpose,
54// including commercial applications, and to alter it and redistribute it
55// freely, subject to the following restrictions:
56//
57// 1. The origin of this software must not be misrepresented; you must not
58// claim that you wrote the original software. If you use this software
59// in a product, an acknowledgement in the product documentation would be
60// appreciated but is not required.
61// 2. Altered source versions must be plainly marked as such, and must not be
62// misrepresented as being the original software.
63// 3. This notice may not be removed or altered from any source distribution.
64#if defined(_WIN32)
65class Semaphore {
66 private:
67 void* m_hSema;
68
69 Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
70 Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
71
72 public:
73 Semaphore(int initialCount = 0)
74 {
75 assert(initialCount >= 0);
76 const long maxLong = 0x7fffffff;
77 m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
78 assert(m_hSema);
79 }
80
81 ~Semaphore() { CloseHandle(m_hSema); }
82
83 bool wait()
84 {
85 const unsigned long infinite = 0xffffffff;
86 return WaitForSingleObject(m_hSema, infinite) == 0;
87 }
88
89 bool try_wait() { return WaitForSingleObject(m_hSema, 0) == 0; }
90
91 bool timed_wait(std::uint64_t usecs) { return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; }
92
93 void signal(int count = 1)
94 {
95 while (!ReleaseSemaphore(m_hSema, count, nullptr))
96 ;
97 }
98};
99#elif defined(__MACH__)
100//---------------------------------------------------------
101// Semaphore (Apple iOS and OSX)
102// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
103//---------------------------------------------------------
104class Semaphore {
105 private:
106 semaphore_t m_sema;
107
108 Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
109 Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
110
111 public:
112 Semaphore(int initialCount = 0)
113 {
114 assert(initialCount >= 0);
115 kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
116 assert(rc == KERN_SUCCESS);
117 (void)rc;
118 }
119
120 ~Semaphore() { semaphore_destroy(mach_task_self(), m_sema); }
121
122 bool wait() { return semaphore_wait(m_sema) == KERN_SUCCESS; }
123
124 bool try_wait() { return timed_wait(0); }
125
126 bool timed_wait(std::uint64_t timeout_usecs)
127 {
128 mach_timespec_t ts;
129 ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
130 ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
131
132 // added in OSX 10.10:
133 // https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
134 kern_return_t rc = semaphore_timedwait(m_sema, ts);
135 return rc == KERN_SUCCESS;
136 }
137
138 void signal()
139 {
140 while (semaphore_signal(m_sema) != KERN_SUCCESS)
141 ;
142 }
143
144 void signal(int count)
145 {
146 while (count-- > 0) {
147 while (semaphore_signal(m_sema) != KERN_SUCCESS)
148 ;
149 }
150 }
151};
152#elif defined(__unix__) || defined(__wasm__)
153//---------------------------------------------------------
154// Semaphore (POSIX, Linux)
155//---------------------------------------------------------
156class Semaphore {
157 private:
158 sem_t m_sema;
159
160 Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
161 Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
162
163 public:
164 Semaphore(int initialCount = 0)
165 {
166 assert(initialCount >= 0);
167 int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
168 assert(rc == 0);
169 (void)rc;
170 }
171
172 ~Semaphore() { sem_destroy(&m_sema); }
173
174 bool wait()
175 {
176 // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
177 int rc;
178 do {
179 rc = sem_wait(&m_sema);
180 } while (rc == -1 && errno == EINTR);
181 return rc == 0;
182 }
183
184 bool try_wait()
185 {
186 int rc;
187 do {
188 rc = sem_trywait(&m_sema);
189 } while (rc == -1 && errno == EINTR);
190 return rc == 0;
191 }
192
193 bool timed_wait(std::uint64_t usecs)
194 {
195 struct timespec ts;
196 const int usecs_in_1_sec = 1000000;
197 const int nsecs_in_1_sec = 1000000000;
198#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
199 clock_gettime(CLOCK_MONOTONIC, &ts);
200#else
201 clock_gettime(CLOCK_REALTIME, &ts);
202#endif
203 ts.tv_sec += (time_t)(usecs / usecs_in_1_sec);
204 ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000;
205 // sem_timedwait bombs if you have more than 1e9 in tv_nsec
206 // so we have to clean things up before passing it in
207 if (ts.tv_nsec >= nsecs_in_1_sec) {
208 ts.tv_nsec -= nsecs_in_1_sec;
209 ++ts.tv_sec;
210 }
211
212 int rc;
213 do {
214#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
215 rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts);
216#else
217 rc = sem_timedwait(&m_sema, &ts);
218#endif
219 } while (rc == -1 && errno == EINTR);
220 return rc == 0;
221 }
222
223 void signal()
224 {
225 while (sem_post(&m_sema) == -1)
226 ;
227 }
228
229 void signal(int count)
230 {
231 while (count-- > 0) {
232 while (sem_post(&m_sema) == -1)
233 ;
234 }
235 }
236};
237#else
238#error Unsupported platform! (No semaphore wrapper available)
239#endif
240
241} // end namespace details
242
243//---------------------------------------------------------
244// LightweightSemaphore
245//---------------------------------------------------------
247 public:
248 typedef std::make_signed<std::size_t>::type ssize_t;
249
250 private:
251 std::atomic<ssize_t> m_count;
252 details::Semaphore m_sema;
253 int m_maxSpins;
254
255 bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1)
256 {
257 ssize_t oldCount;
258 int spin = m_maxSpins;
259 while (--spin >= 0) {
260 oldCount = m_count.load(std::memory_order_relaxed);
261 if ((oldCount > 0) && m_count.compare_exchange_strong(
262 oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
263 return true;
264 std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop.
265 }
266 oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
267 if (oldCount > 0)
268 return true;
269 if (timeout_usecs < 0) {
270 if (m_sema.wait())
271 return true;
272 }
273 if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs))
274 return true;
275 // At this point, we've timed out waiting for the semaphore, but the
276 // count is still decremented indicating we may still be waiting on
277 // it. So we have to re-adjust the count, but only if the semaphore
278 // wasn't signaled enough times for us too since then. If it was, we
279 // need to release the semaphore too.
280 while (true) {
281 oldCount = m_count.load(std::memory_order_acquire);
282 if (oldCount >= 0 && m_sema.try_wait())
283 return true;
284 if (oldCount < 0 && m_count.compare_exchange_strong(
285 oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
286 return false;
287 }
288 }
289
290 ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1)
291 {
292 assert(max > 0);
293 ssize_t oldCount;
294 int spin = m_maxSpins;
295 while (--spin >= 0) {
296 oldCount = m_count.load(std::memory_order_relaxed);
297 if (oldCount > 0) {
298 ssize_t newCount = oldCount > max ? oldCount - max : 0;
299 if (m_count.compare_exchange_strong(
300 oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
301 return oldCount - newCount;
302 }
303 std::atomic_signal_fence(std::memory_order_acquire);
304 }
305 oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
306 if (oldCount <= 0) {
307 if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) ||
308 (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) {
309 while (true) {
310 oldCount = m_count.load(std::memory_order_acquire);
311 if (oldCount >= 0 && m_sema.try_wait())
312 break;
313 if (oldCount < 0 &&
314 m_count.compare_exchange_strong(
315 oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
316 return 0;
317 }
318 }
319 }
320 if (max > 1)
321 return 1 + tryWaitMany(max - 1);
322 return 1;
323 }
324
325 public:
326 LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000)
327 : m_count(initialCount)
328 , m_maxSpins(maxSpins)
329 {
330 assert(initialCount >= 0);
331 assert(maxSpins >= 0);
332 }
333
334 bool tryWait()
335 {
336 ssize_t oldCount = m_count.load(std::memory_order_relaxed);
337 while (oldCount > 0) {
338 if (m_count.compare_exchange_weak(
339 oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
340 return true;
341 }
342 return false;
343 }
344
345 bool wait() { return tryWait() || waitWithPartialSpinning(); }
346
347 bool wait(std::int64_t timeout_usecs) { return tryWait() || waitWithPartialSpinning(timeout_usecs); }
348
349 // Acquires between 0 and (greedily) max, inclusive
350 ssize_t tryWaitMany(ssize_t max)
351 {
352 assert(max >= 0);
353 ssize_t oldCount = m_count.load(std::memory_order_relaxed);
354 while (oldCount > 0) {
355 ssize_t newCount = oldCount > max ? oldCount - max : 0;
356 if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
357 return oldCount - newCount;
358 }
359 return 0;
360 }
361
362 // Acquires at least one, and (greedily) at most max
363 ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs)
364 {
365 assert(max >= 0);
366 ssize_t result = tryWaitMany(max);
367 if (result == 0 && max > 0)
368 result = waitManyWithPartialSpinning(max, timeout_usecs);
369 return result;
370 }
371
372 ssize_t waitMany(ssize_t max)
373 {
374 ssize_t result = waitMany(max, -1);
375 assert(result > 0);
376 return result;
377 }
378
379 void signal(ssize_t count = 1)
380 {
381 assert(count >= 0);
382 ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release);
383 ssize_t toRelease = -oldCount < count ? -oldCount : count;
384 if (toRelease > 0) {
385 m_sema.signal((int)toRelease);
386 }
387 }
388
389 std::size_t availableApprox() const
390 {
391 ssize_t count = m_count.load(std::memory_order_relaxed);
392 return count > 0 ? static_cast<std::size_t>(count) : 0;
393 }
394};
395
396} // end namespace moodycamel
Definition: lightweightsemaphore.h:246