/**************************************************************************/1/* semaphore.h */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#pragma once3132#include "core/typedefs.h"3334#ifdef THREADS_ENABLED3536#ifdef DEBUG_ENABLED37#include "core/error/error_macros.h"38#endif3940#ifdef MINGW_ENABLED41#define MINGW_STDTHREAD_REDUNDANCY_WARNING42#include "thirdparty/mingw-std-threads/mingw.condition_variable.h"43#include "thirdparty/mingw-std-threads/mingw.mutex.h"44#define THREADING_NAMESPACE mingw_stdthread45#else46#include <condition_variable>47#include <mutex>48#define THREADING_NAMESPACE std49#endif5051class Semaphore {52private:53mutable THREADING_NAMESPACE::mutex mutex;54mutable THREADING_NAMESPACE::condition_variable condition;55mutable uint32_t count = 0; // Initialized as locked.56#ifdef DEBUG_ENABLED57mutable uint32_t awaiters = 0;58#endif5960public:61_ALWAYS_INLINE_ void post(uint32_t p_count = 1) const {62std::lock_guard lock(mutex);63count += p_count;64for (uint32_t i = 0; i < p_count; ++i) {65condition.notify_one();66}67}6869_ALWAYS_INLINE_ void wait() const {70THREADING_NAMESPACE::unique_lock lock(mutex);71#ifdef DEBUG_ENABLED72++awaiters;73#endif74while (!count) { // Handle spurious wake-ups.75condition.wait(lock);76}77--count;78#ifdef DEBUG_ENABLED79--awaiters;80#endif81}8283_ALWAYS_INLINE_ bool try_wait() const {84std::lock_guard lock(mutex);85if (count) {86count--;87return true;88} else {89return false;90}91}9293#ifdef DEBUG_ENABLED94~Semaphore() {95// Destroying an std::condition_variable when not all threads waiting on it have been notified96// invokes undefined behavior (e.g., it may be nicely destroyed or it may be awaited forever.)97// That means other threads could still be running the body of std::condition_variable::wait()98// but already past the safety checkpoint. That's the case for instance if that function is already99// waiting to lock again.100//101// We will make the rule a bit more restrictive and simpler to understand at the same time: there102// should not be any threads at any stage of the waiting by the time the semaphore is destroyed.103//104// We do so because of the following reasons:105// - We have the guideline that threads must be awaited (i.e., completed), so the waiting thread106// must be completely done by the time the thread controlling it finally destroys the semaphore.107// Therefore, only a coding mistake could make the program run into such a attempt at premature108// destruction of the semaphore.109// - In scripting, given that Semaphores are wrapped by RefCounted classes, in general it can't110// happen that a thread is trying to destroy a Semaphore while another is still doing whatever with111// it, so the simplification is mostly transparent to script writers.112// - The redefined rule can be checked for failure to meet it, which is what this implementation does.113// This is useful to detect a few cases of potential misuse; namely:114// a) In scripting:115// * The coder is naughtily dealing with the reference count causing a semaphore to die prematurely.116// * The coder is letting the project reach its termination without having cleanly finished threads117// that await on semaphores (or at least, let the usual semaphore-controlled loop exit).118// b) In the native side, where Semaphore is not a ref-counted beast and certain coding mistakes can119// lead to its premature destruction as well.120//121// Let's let users know they are doing it wrong, but apply a, somewhat hacky, countermeasure against UB122// in debug builds.123std::lock_guard lock(mutex);124if (awaiters) {125WARN_PRINT(126"A Semaphore object is being destroyed while one or more threads are still waiting on it.\n"127"Please call post() on it as necessary to prevent such a situation and so ensure correct cleanup.");128// And now, the hacky countermeasure (i.e., leak the condition variable).129new (&condition) THREADING_NAMESPACE::condition_variable();130}131}132#endif133};134135#else // No threads.136137class Semaphore {138public:139void post(uint32_t p_count = 1) const {}140void wait() const {}141bool try_wait() const {142return true;143}144};145146#endif // THREADS_ENABLED147148149