ALib C++ Framework
by
Library Version: 2605 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
dbgasserters.hpp
Go to the documentation of this file.
1//==================================================================================================
2/// \file
3/// This header-file is part of module \alib_threads of the \aliblong.
4///
5/// Copyright 2013-2026 A-Worx GmbH, Germany.
6/// Published under #"mainpage_license".
7//==================================================================================================
8#if !ALIB_SINGLE_THREADED && ALIB_DEBUG
9
10ALIB_EXPORT namespace alib::threads {
11
12//==================================================================================================
13/// This type is used for debugging and asserting \alib lock (mutex) types.
14/// With debug compilations the non-shared lock types hold one member of this struct, which
15/// aggregates all debug information.
16//==================================================================================================
18
19 /// The name of this Lock.
20 /// Set to <em> "<unnamed>"</em> by default.
21 /// Used for debug-output.
22 /// For automatic pruning of changes to this name, macro #"ALIB_DBG" should be used.
23 const char* Name ="<unnamed>";
24 std::atomic<int> ActionCounter{1}; ///< Counter of the actions.
25 std::atomic<int> CntAcquirements{0}; ///< The number of shared acquirements.
26
27 /// Collects caller info and the sequence number of actions.
28 struct ActionInfo {
29 CallerInfo CI; ///< The caller information.
30 int ActionNo = -1; ///< The sequence number of the action.
31 };
32
33 ActionInfo Acq ; ///< Source location of the most recent acquirement.
34 ActionInfo Rel ; ///< Source location of the most recent release.
35
36 /// The format string used to write exceptions to the console.
37 /// This string can be changed if the source information is not "clickable" in a user's
38 /// development environment.<br>
39 ///
40 /// The default string is optimized for
41 /// \https{JetBrains CLion,www.jetbrains.com/clion} and is defined as:
42 /** \verbatim
43Multi-Threading {} in Lock \"{}\"
44 Message: {}
45 In (Member-)Function: {}
46 Is Owned: {} ({})
47
48 Called By: {}::{}
49 At: {}:{}
50 Thread: {}
51
52 Latest Acquisition By: {}::{}
53 At: {}:{}
54 Seq / Thread: {} / {}
55 Latest Release By: {}:{}
56 At: {}:{}
57 Seq / Thread: {} / {}
58 \endverbatim
59 <p>
60 The placeholder fields that this format string refers to are set as follows:
61 - \c 0: String "Assertion" or "Warning"
62 - \c 1: Debug-name of the lock.
63 - \c 2: Headline.
64 - \c 3: Function name.
65 - \c 4: Is acquired (true/false).
66 - \c 5: number of acquirements.
67 - \c 6-10: #"CallerInfo" of caller.
68 - \c 11-16: #"DbgLockAsserter::ActionInfo" of the latest acquisition.
69 - \c 17-22: #"DbgLockAsserter::ActionInfo" of the latest release.
70*/
72 static const char* ASSERTION_FORMAT;
73
74
75 /// This is a threshold that causes non-timed Acquire() methods to raise a \alib_warning in
76 /// debug-builds in case a thread is blocked longer than the given duration.
77 ///
78 /// To disable warnings in cases that high block times are suitable, set this value to \c 0.
79 /// The default value is two seconds.
80 Ticks::Duration WaitTimeLimit = Ticks::Duration::FromAbsoluteSeconds(2);
81
82 /// Limit of recursions. If the limit is reached or a multiple of it, an
83 /// \alib_warning is raised.
84 /// Defaults is \c 10. To disable, set to \c 0.<p>
85 /// Available only in debug versions of \alib.
87
88 /// Destructor.
89 virtual ~DbgLockAsserter() {
90 if( CntAcquirements.load() > 0 )
91 DoAssert( 0, ALIB_CALLER, ALIB_CALLER, "Destructing acquired lock" );
92 }
93
94 /// Returns a pointer the owning thread.<p>
95 /// Available only in debug-builds.
96 /// @return Pointer to the owning thread, or \c nullptr if not owned.
98 Thread* GetOwner() const;
99
100 /// Returns \c true if the current owner is the current thread.<p>
101 /// Available only in debug-builds.
102 /// @return \c true, if the lock is owned by this thread, \c false if it is owned by
103 /// another thread or not owned.
105 { return CntAcquirements.load() > 0 && Acq.CI.ThreadID == std::this_thread::get_id(); }
106
107 /// Returns \c true if this is a non-recursive lock or a recursive instance which is acquired
108 /// exactly once.<br>
109 /// Available only in debug-builds.
110 ///
111 /// \note
112 /// This method is not (and cannot) be synchronized. Consequently, a reliable result
113 /// is only guaranteed in case #".IsOwnedByCurrentThread" returns \c true before this method
114 /// is invoked.
115 ///
116 /// @return \c true if the next release will free this lock, \c false otherwise.
117 bool WillRelease() const noexcept { return CntAcquirements.load()== 1; }
118
119 /// Collects assertion info and #"alib_mod_assert;raises a warning or error".
120 /// @see
121 /// Field #".ASSERTION_FORMAT" which allows changing the output format to achieve 'clickable'
122 /// assertion messages.
123 /// @param type 0= assertion, 1= warning.
124 /// @param assertCI Location where the assertion is placed.
125 /// @param ci Location of the call to the method that asserted.
126 /// @param headline The message.
128 virtual
129 void DoAssert (int type, const CallerInfo& assertCI, const CallerInfo& ci,
130 const char* headline );
131
132 /// Asserts that #"CntAcquirements" is not \c 0
133 /// @param assertCI Location where the assertion is placed.
134 /// @param ci Location of the call to the method that asserted.
135 void AssertOwned (const CallerInfo& assertCI, const CallerInfo& ci )
136 {
137 if( CntAcquirements.load() == 0 )
138 DoAssert( 0, assertCI, ci, "Not acquired" );
139 }
140
141 /// Sets the given caller as the current owner. Asserts that #"CntAcquirements" is \c 0
142 /// when called.
143 /// @param assertCI Location where ownership was implemented (usually the lock).
144 /// @param requestCI Location where ownership was requested (the caller of \p{assertCI})
145 void SetOwner (const CallerInfo& assertCI, const CallerInfo& requestCI ) {
146 if( CntAcquirements.load() > 0 )
147 DoAssert( 0, assertCI, requestCI, "Already (still) owned." );
148 Acq.CI = requestCI;
149 Acq.ActionNo= ActionCounter.fetch_add(1);
150 CntAcquirements.fetch_add(1);
151 }
152
153 /// Sets the given caller as the current owner. Asserts that either #"CntAcquirements" is \c 0
154 /// or the current owner is the same as in parameter \p{requestCI}.
155 /// @param assertCI Location where ownership was implemented (usually the lock).
156 /// @param requestCI Location where ownership was requested (the caller of \p{assertCI})
158 void SetRecursiveOwner (const CallerInfo& assertCI, const CallerInfo& requestCI );
159
160 /// Asserts that this lock is owned by the thread in \p{ci}.
161 /// @param assertCI Location where the release is implemented.
162 /// @param requestCI Location where the release was requested (the caller of \p{assertCI})
163 void Release (const CallerInfo& assertCI, const CallerInfo& requestCI) {
164 if( CntAcquirements.load() == 0 || requestCI.ThreadID != Acq.CI.ThreadID )
165 DoAssert( 0, assertCI, requestCI, "Release without having ownership");
166 Rel.CI = requestCI;
167 Rel.ActionNo= ActionCounter.fetch_add(1);
168 CntAcquirements.fetch_sub(1);
169 }
170
171 /// Asserts that this lock is not owned by the thread in \p{ci}.
172 /// @param assertCI Location where the assertion is placed.
173 /// @param ci Location of the call to the method that asserted.
174 /// @param headline The message.
175 void AssertNotOwning(const CallerInfo& assertCI, const CallerInfo& ci, const char* headline )
176 {
177 if( CntAcquirements.load() > 0 && ci.ThreadID == Acq.CI.ThreadID )
178 DoAssert( 0, assertCI, ci, headline);
179 }
180}; // struct DbgLockAsserter
181
182//==================================================================================================
183/// This type is used for debugging and asserting \alib lock (mutex) types.
184/// With debug compilations the shared lock types hold one member of this struct, which
185/// aggregates all debug information.
186//==================================================================================================
188 ActionInfo SAcq; ///< The most recent shared acquirement's caller.
189 ActionInfo SRel; ///< The most recent shared release caller.
190 std::atomic<int> CntSharedAcquirements{0}; ///< The number of shared acquirements.
191
192 /// The format string used to write exceptions to the console.
193 /// This string can be changed if the source information is not "clickable" in a user's
194 /// development environment.<br>
195 ///
196 /// The default string is optimized for
197 /// \https{JetBrains CLion,www.jetbrains.com/clion} and is defined as:
198 /** \verbatim
199Multi-Threading {} in Shared-Lock \"{}\"
200 Message: {}
201 In (Member-)Function: {}
202 Is Owned: {} ({})
203 Is Shared Owned: {} ({})
204
205 Called By: {}::{}
206 At: {}:{}
207 Thread: {}
208
209 Latest Acquisition By: {}::{}
210 At: {}:{}
211 Seq / Thread: {} / {}
212 Latest Release By: {}::{}
213 At: {}:{}
214 Seq / Thread: {} / {}
215
216 Latest Shared Acquisition By: {}::{}
217 At: {}:{}
218 Seq / Thread: {} / {}
219 Latest SharedRelease By: {}::{}
220 At: {}:{}
221 Seq / Thread: {} / {}
222 \endverbatim
223 <p>
224 The placeholder fields that this format string refers to are set as follows:
225
226 - \c 0: String "Assertion" or "Warning"
227 - \c 1: Debug-name of the lock.
228 - \c 2: Headline.
229 - \c 3: Function name.
230 - \c 4: Is acquired (true/false).
231 - \c 5: number of acquirements.
232 - \c 6: Is shared acquired (true/false).
233 - \c 7: number of shared-acquirements.
234 - \c 8-12: #"CallerInfo" of caller.
235 - \c 13-18: #"DbgLockAsserter::ActionInfo" of the latest acquisition.
236 - \c 19-24: #"DbgLockAsserter::ActionInfo" of the latest release.
237 - \c 25-30: #"DbgLockAsserter::ActionInfo" of the latest shared-acquisition.
238 - \c 31-36: #"DbgLockAsserter::ActionInfo" of the latest shared-release.
239 */
241 static const char* ASSERTION_FORMAT_SHARED;
242
243
244 /// Destructor.
245 virtual ~DbgSharedLockAsserter() override {}
246
247 /// Assembles assertion info and #"alib_mod_assert;raises a warning or an error".
248 /// @see
249 /// Inherited field #"DbgLockAsserter::ASSERTION_FORMAT" which allows changing the output
250 /// format to achieve 'clickable' assertion messages.
251 /// @param type 0= assertion, 1= warning.
252 /// @param assertCI Location where the assertion is placed.
253 /// @param ci Location of the call to the method that asserted.
254 /// @param headline The message.
256 void DoAssert (int type, const CallerInfo& assertCI, const CallerInfo& ci,
257 const char* headline ) override;
258
259 /// Returns \c true if currently a reader is registered. This method is used to
260 /// create assertions. of course, to detect assertions, it would be more efficient to check
261 /// if shared ownership is with the current thread, but a check on this would cost
262 /// a lot of overhead to realize. This way, at least assertions can be raised when no other
263 /// thread acquired this lock in shared mode.<p>
264 /// Available only in debug-builds.
265 /// @return \c true, if the lock is owned by this thread, \c false if it is owned by
266 /// another thread or not owned.
267 bool IsSharedOwnedByAnyThread() const { return CntSharedAcquirements.load() > 0; }
268
269 /// Sets the given caller as the current owner. Asserts that #"CntAcquirements" is \c 0
270 /// when called.
271 /// @param assertCI Location where ownership was implemented (usually the lock).
272 /// @param requestCI Location where ownership was requested (the caller of \p{assertCI})
273 void SetOwner (const CallerInfo& assertCI, const CallerInfo& requestCI ) {
274 if( CntSharedAcquirements.load() > 0 )
275 DoAssert( 0, assertCI, requestCI, "Already (still) shared-owned." );
276 DbgLockAsserter::SetOwner( assertCI, requestCI );
277 }
278
279 /// Sets the given caller as a current shared-owner. Asserts that #"CntAcquirements" is \c 0
280 /// when called.
281 /// @param assertCI Location where ownership was implemented (usually the lock).
282 /// @param requestCI Location where ownership was requested (the caller of \p{assertCI})
283 /// @param warnLimit If this number of shared acquisitions is exceeded, a warning is
284 /// given.
285 void SetSharedOwner (const CallerInfo& assertCI, const CallerInfo& requestCI,
286 int warnLimit ) {
287 if( CntAcquirements.load() > 0 )
288 DoAssert( 0, assertCI, requestCI, "Already (still) owned." );
289
290 if ( CntSharedAcquirements.fetch_add(1) >= warnLimit )
291 DoAssert( 1, ALIB_CALLER, requestCI,
292 "Too many parallel shared acquisitions detected. "
293 "A reason might be that shared acquirers do not call ReleaseShared" );
294
295 SAcq.CI = requestCI;
296 SAcq.ActionNo= ActionCounter.fetch_add(1);
297 }
298
299 /// Asserts that this lock is shared-owned by the thread in \p{ci}.
300 /// @param assertCI Location where the release is implemented.
301 /// @param requestCI Location where the release was requested (the caller of \p{assertCI})
302 void ReleaseShared (const CallerInfo& assertCI, const CallerInfo& requestCI) {
303 auto prevCounter= CntSharedAcquirements.fetch_sub(1);
304 if ( prevCounter <= 0 )
305 DoAssert( 0, assertCI, requestCI,
306 "Too many invocations of ReleaseShared (from any thread) without prior acquisition" );
307
308 SRel.CI = requestCI;
309 SRel.ActionNo= ActionCounter.fetch_add(1);
310 }
311
312}; // struct DbgSharedLockAsserter
313
314
315//==================================================================================================
316/// This type is used for debugging and asserting types #"TCondition".
317/// With debug compilations that class holds one member of this struct, which aggregates all
318/// debug information.
319//==================================================================================================
321 /// Collects caller info and the sequence number of actions.
322 struct ActionInfo {
323 CallerInfo CI; ///< The caller information.
324 int ActionNo = -1; ///< The sequence number of the action.
325 };
326
327 const character* Name; ///< The name of this instance.
328 std::atomic<int> ActionCounter{1}; ///< Counter of the actions.
329 Thread* Owner{nullptr}; ///< Tracks the current owner.
330 ActionInfo Acq ; ///< Source location of the most recent acquirement.
331 ActionInfo Rel ; ///< Source location of the most recent release.
332 ActionInfo Wait; ///< The most recent call to
333 ///< #"%WaitForNotification(ALIB_DBG_TAKE_CI)".
334 ActionInfo Notify; ///< The most recent call to #"%ReleaseAndNotify".
335 std::atomic<int> CntWaiters{0}; ///< The number of currently waiting threads.
336 std::thread::id AssertExclusiveWaiter; ///< If set from outside, overloaded methods
337 ///< #"TCondition::WaitForNotification(ALIB_DBG_TAKE_CI)"
338 ///< will raise an assertion in the case they are
339 ///< called from a different thread.
340
341 /// The format string used to write exceptions to the console.
342 /// This string can be changed if the source information is not "clickable" in a user's
343 /// development environment.<br>
344 ///
345 /// The default string is optimized for
346 /// \https{JetBrains CLion,www.jetbrains.com/clion} and is defined as:
347 /** \verbatim
348Assertion failed in method TCondition::{}
349 Message: {}
350 Instance: {}
351
352 Called By: {}::{}
353 At: {}:{}
354 Thread: {}
355
356 Current Owner: {}
357 #Of Waiters: {}
358 Exclusive Waiter: {}
359
360 Latest Acquisition By: {}::{}
361 At: {}:{}
362 Seq / Thread: {} / {}
363 Latest Release By: {}::{}
364 At: {}:{}
365 Seq / Thread: {} / {}
366
367 Latest Wait By: {}::{}
368 At: {}:{}
369 Seq / Thread: {} / {}
370 Latest Notify By: {}::{}
371 At: {}:{}
372 Seq / Thread: {} / {}
373 \endverbatim
374*/
376 static const char* ASSERTION_FORMAT;
377
378 /// Writes assertion info and calls #"ALIB_ASSERT".
379 /// @see
380 /// Inherited field #"DbgLockAsserter::ASSERTION_FORMAT" which allows changing the output
381 /// format to achieve 'clickable' assertion messages.
382 /// @param cond The condition that is to be met.
383 /// @param assertCI Location where the assertion is placed.
384 /// @param ci Location of the call to the method that asserted.
385 /// @param headline The message.
387 void Assert( bool cond, const CallerInfo& assertCI, const CallerInfo& ci,
388 const char* headline );
389
390 /// Returns \c true if the current owner is the current thread.<p>
391 /// Available only in debug-builds.
392 /// @return \c true, if the lock is owned by this thread, \c false if it is owned by
393 /// another thread or not owned.
395 { return Owner != nullptr && Owner == Thread::GetCurrent() ; }
396
397}; // struct DbgConditionAsserter
398
399} // namespace [alib::threads]
400
401#endif // !ALIB_SINGLE_THREADED && ALIB_DEBUG
#define ALIB_DLL
#define ALIB_CALLER
#define ALIB_EXPORT
static Thread * GetCurrent()
Definition thread.hpp:294
lang::CallerInfo CallerInfo
Type alias in namespace #"%alib".
characters::character character
Type alias in namespace #"%alib".
std::thread::id ThreadID
The ID of the calling thread.
Collects caller info and the sequence number of actions.
int ActionNo
The sequence number of the action.
ActionInfo Rel
Source location of the most recent release.
std::atomic< int > ActionCounter
Counter of the actions.
void Assert(bool cond, const CallerInfo &assertCI, const CallerInfo &ci, const char *headline)
const character * Name
The name of this instance.
ActionInfo Acq
Source location of the most recent acquirement.
Thread * Owner
Tracks the current owner.
ActionInfo Notify
The most recent call to #"%ReleaseAndNotify".
std::atomic< int > CntWaiters
The number of currently waiting threads.
Collects caller info and the sequence number of actions.
int ActionNo
The sequence number of the action.
CallerInfo CI
The caller information.
void AssertOwned(const CallerInfo &assertCI, const CallerInfo &ci)
static const char * ASSERTION_FORMAT
ActionInfo Rel
Source location of the most recent release.
virtual void DoAssert(int type, const CallerInfo &assertCI, const CallerInfo &ci, const char *headline)
ActionInfo Acq
Source location of the most recent acquirement.
void AssertNotOwning(const CallerInfo &assertCI, const CallerInfo &ci, const char *headline)
std::atomic< int > CntAcquirements
The number of shared acquirements.
bool WillRelease() const noexcept
void SetRecursiveOwner(const CallerInfo &assertCI, const CallerInfo &requestCI)
std::atomic< int > ActionCounter
Counter of the actions.
virtual ~DbgLockAsserter()
Destructor.
void Release(const CallerInfo &assertCI, const CallerInfo &requestCI)
void SetOwner(const CallerInfo &assertCI, const CallerInfo &requestCI)
ActionInfo SAcq
The most recent shared acquirement's caller.
virtual ~DbgSharedLockAsserter() override
Destructor.
void DoAssert(int type, const CallerInfo &assertCI, const CallerInfo &ci, const char *headline) override
void SetOwner(const CallerInfo &assertCI, const CallerInfo &requestCI)
static const char * ASSERTION_FORMAT_SHARED
void SetSharedOwner(const CallerInfo &assertCI, const CallerInfo &requestCI, int warnLimit)
std::atomic< int > CntSharedAcquirements
The number of shared acquirements.
void ReleaseShared(const CallerInfo &assertCI, const CallerInfo &requestCI)
ActionInfo SRel
The most recent shared release caller.