HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
unfair_mutex_impl.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
9#pragma once
10
11#include "unfair_mutex_intf.hpp"
12#include "global_state.hpp"
13#include "../utility/utility.hpp"
14#include "../macros.hpp"
15#include <atomic>
16#include <memory>
17#include <format>
18#include <mutex>
19#include <vector>
20#include <algorithm>
21
22hi_export_module(hikogui.concurrency.unfair_mutex : impl);
23
24hi_export namespace hi { inline namespace v1 {
25namespace detail {
26
27inline unfair_mutex_impl<false> unfair_mutex_deadlock_mutex;
28
29thread_local inline std::vector<void *> unfair_mutex_deadlock_stack;
30
36inline std::vector<std::pair<void *, void *>> unfair_mutex_deadlock_lock_graph;
37
38[[nodiscard]] inline void *unfair_mutex_deadlock_check_graph(void *object) noexcept
39{
40 hi_assert_not_null(object);
41
42 auto const lock = std::scoped_lock(detail::unfair_mutex_deadlock_mutex);
43
44 for (auto const before : unfair_mutex_deadlock_stack) {
45 auto correct_order = std::make_pair(before, object);
46 auto const reverse_order = std::make_pair(object, before);
47
49 unfair_mutex_deadlock_lock_graph.cbegin(), unfair_mutex_deadlock_lock_graph.cend(), correct_order)) {
50 // The object has been locked in the correct order in comparison to `before`.
51 continue;
52 }
53
55 unfair_mutex_deadlock_lock_graph.cbegin(), unfair_mutex_deadlock_lock_graph.cend(), reverse_order)) {
56 // The object has been locked in reverse order in comparison to `before`.
57 return before;
58 }
60 // Insert the new 'correct' order in the sorted lock_graph.
61 auto const it =
62 std::upper_bound(unfair_mutex_deadlock_lock_graph.cbegin(), unfair_mutex_deadlock_lock_graph.cend(), correct_order);
63 unfair_mutex_deadlock_lock_graph.insert(it, std::move(correct_order));
64 }
65 return nullptr;
66}
67
68} // namespace detail
69
75hi_export inline void *unfair_mutex_deadlock_lock(void *object) noexcept
76{
78 // thread_local variables used by `stack` do not work on MSVC after main() returns.
79 return nullptr;
80 }
81
82 hi_assert_not_null(object);
83
84 if (std::count(detail::unfair_mutex_deadlock_stack.begin(), detail::unfair_mutex_deadlock_stack.end(), object) != 0) {
85 // `object` already locked by the current thread.
86 return object;
87 }
88
89 if (auto before = detail::unfair_mutex_deadlock_check_graph(object)) {
90 // Trying to lock `object` after `before` in previously reversed order
91 return before;
92 }
93
94 detail::unfair_mutex_deadlock_stack.push_back(object);
95 return nullptr;
96}
97
102hi_export inline bool unfair_mutex_deadlock_unlock(void *object) noexcept
103{
105 // thread_local variables used by `stack` do not work on MSVC when main() returns.
106 return true;
107 }
108
109 hi_assert_not_null(object);
110
111 // Trying to unlock `object`, but nothing on this thread was locked.
112 if (detail::unfair_mutex_deadlock_stack.empty()) {
113 return false;
114 }
115
116 // Trying to unlock `object`, but unlocking in different order.
117 if (detail::unfair_mutex_deadlock_stack.back() != object) {
118 return false;
119 }
120
121 detail::unfair_mutex_deadlock_stack.pop_back();
122 return true;
123}
124
131hi_export inline void unfair_mutex_deadlock_remove_object(void *object) noexcept
132{
133 hi_assert_not_null(object);
134
136 // thread_local variables used by `lock_graph` do not work on MSVC when main() returns.
137 return;
138 }
139
140 auto const lock = std::scoped_lock(detail::unfair_mutex_deadlock_mutex);
141 std::erase_if(detail::unfair_mutex_deadlock_lock_graph, [object](auto const& item) {
142 return item.first == object or item.second == object;
143 });
144}
145
149hi_export inline void unfair_mutex_deadlock_clear_stack() noexcept
150{
152 // thread_local variables used by `stack` do not work on MSVC when main() returns.
153 return;
154 }
155
156 detail::unfair_mutex_deadlock_stack.clear();
157}
158
162hi_export inline void unfair_mutex_deadlock_clear_graph() noexcept
163{
165 // thread_local variables used by `lock_graph` do not work on MSVC when main() returns.
166 return;
167 }
168
169 auto const lock = std::scoped_lock(detail::unfair_mutex_deadlock_mutex);
170 detail::unfair_mutex_deadlock_lock_graph.clear();
171}
172
173template<bool UseDeadLockDetector>
174inline unfair_mutex_impl<UseDeadLockDetector>::~unfair_mutex_impl()
175{
176 hi_axiom(not is_locked());
177 if constexpr (UseDeadLockDetector) {
179 }
180}
181
182template<bool UseDeadLockDetector>
183inline bool unfair_mutex_impl<UseDeadLockDetector>::is_locked() const noexcept
184{
185 return semaphore.load(std::memory_order::relaxed) != 0;
186}
187
188template<bool UseDeadLockDetector>
189inline void unfair_mutex_impl<UseDeadLockDetector>::lock() noexcept
190{
191 if constexpr (UseDeadLockDetector) {
192 auto const other = unfair_mutex_deadlock_lock(this);
193 hi_assert(other != this, "This mutex is already locked.");
194 hi_assert(other == nullptr, "Potential dead-lock because of different lock ordering of mutexes.");
195 }
196
197 hi_axiom(holds_invariant());
198
199 // Switch to 1 means there are no waiters.
200 semaphore_value_type expected = 0;
201 if (not semaphore.compare_exchange_strong(expected, 1, std::memory_order::acquire)) {
202 [[unlikely]] lock_contended(expected);
203 }
204
205 hi_axiom(holds_invariant());
206}
207
215template<bool UseDeadLockDetector>
216[[nodiscard]] inline bool unfair_mutex_impl<UseDeadLockDetector>::try_lock() noexcept
217{
218 if constexpr (UseDeadLockDetector) {
219 auto const other = unfair_mutex_deadlock_lock(this);
220 hi_assert(other != this, "This mutex is already locked.");
221 hi_assert(other == nullptr, "Potential dead-lock because of different lock ordering of mutexes.");
222 }
223
224 hi_axiom(holds_invariant());
225
226 // Switch to 1 means there are no waiters.
227 semaphore_value_type expected = 0;
228 if (not semaphore.compare_exchange_strong(expected, 1, std::memory_order::acquire)) {
229 hi_axiom(holds_invariant());
230
231 if constexpr (UseDeadLockDetector) {
232 hi_assert(unfair_mutex_deadlock_unlock(this), "Unlock is not done in reverse order.");
233 }
234
235 [[unlikely]] return false;
236 }
237
238 hi_axiom(holds_invariant());
239 return true;
240}
241
242template<bool UseDeadLockDetector>
244{
245 if constexpr (UseDeadLockDetector) {
246 hi_assert(unfair_mutex_deadlock_unlock(this), "Unlock is not done in reverse order.");
247 }
248
249 hi_axiom(holds_invariant());
250
251 if (semaphore.fetch_sub(1, std::memory_order::relaxed) != 1) {
252 [[unlikely]] semaphore.store(0, std::memory_order::release);
253
254 semaphore.notify_one();
255 } else {
256 atomic_thread_fence(std::memory_order::release);
257 }
258
259 hi_axiom(holds_invariant());
260}
261
262template<bool UseDeadLockDetector>
263[[nodiscard]] inline bool unfair_mutex_impl<UseDeadLockDetector>::holds_invariant() const noexcept
264{
265 return semaphore.load(std::memory_order::relaxed) <= 2;
266}
267
268template<bool UseDeadLockDetector>
269hi_no_inline inline void unfair_mutex_impl<UseDeadLockDetector>::lock_contended(semaphore_value_type expected) noexcept
270{
271 hi_axiom(holds_invariant());
272
273 do {
274 auto const should_wait = expected == 2;
275
276 // Set to 2 when we are waiting.
277 expected = 1;
278 if (should_wait || semaphore.compare_exchange_strong(expected, 2)) {
279 hi_axiom(holds_invariant());
280 semaphore.wait(2);
281 }
282
283 hi_axiom(holds_invariant());
284 // Set to 2 when acquiring the lock, so that during unlock we wake other waiting threads.
285 expected = 0;
286 } while (!semaphore.compare_exchange_strong(expected, 2));
287}
288
289}} // namespace hi::v1
An atomic access global variable for quick access to state of the system.
bool is_system_shutting_down() noexcept
Check if the HikoGUI system is being shut down.
Definition global_state.hpp:222
@ end
Start from the end of the file.
@ begin
Start from the beginning of the file.
@ other
The gui_event does not have associated data.
The HikoGUI namespace.
Definition array_generic.hpp:20
hi_export void unfair_mutex_deadlock_remove_object(void *object) noexcept
Remove the object from the detection.
Definition unfair_mutex_impl.hpp:131
hi_export void unfair_mutex_deadlock_clear_stack() noexcept
Clear the stack.
Definition unfair_mutex_impl.hpp:149
hi_export void * unfair_mutex_deadlock_lock(void *object) noexcept
Lock an object on this thread.
Definition unfair_mutex_impl.hpp:75
hi_export void unfair_mutex_deadlock_clear_graph() noexcept
Clear the graph.
Definition unfair_mutex_impl.hpp:162
hi_export bool unfair_mutex_deadlock_unlock(void *object) noexcept
Unlock an object on this thread.
Definition unfair_mutex_impl.hpp:102
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
An unfair mutex This is a fast implementation of a mutex which does not fairly arbitrate between mult...
Definition unfair_mutex_intf.hpp:38
bool try_lock() noexcept
When try_lock() is called from a thread that already owns the lock it will return false.
Definition unfair_mutex_impl.hpp:216
T binary_search(T... args)
T count(T... args)
T lock(T... args)
T make_pair(T... args)
T move(T... args)
T upper_bound(T... args)