Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
Synchronized.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Execution_Synchronized_h_
5#define _Stroika_Foundation_Execution_Synchronized_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <compare>
10#include <concepts>
11#include <functional>
12#include <mutex>
13#include <optional>
14#include <shared_mutex>
15#include <type_traits>
16
17#include "Stroika/Foundation/Common/Common.h"
20#include "Stroika/Foundation/Execution/Common.h"
23
24/**
25 * \file
26 *
27 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
28 *
29 * TODO:
30 * @todo Deadlock from two threads doing UpgradeLockNonAtomically(quietly) is ??? detectable, so in DEBUG builds translate that to an
31 * assert error?
32 *
33 * @todo http://stroika-bugs.sophists.com/browse/STK-613 - Synchronized<>::ReadableReference and WritableReference could be more efficient if not subclassing each other
34 *
35 * @todo http://stroika-bugs.sophists.com/browse/STK-657 - experiment with some sort of shared_recursive_mutex - not sure good idea in general, but maybe a limited form can be used in synchronized
36 *
37 * @todo Tons of cleanups, orthogonality, docs, etc.
38 *
39 * @todo consider/doc choice on explicit operator T ()
40 *
41 */
42
44
45 /**
46 * The type InternallySynchronized is intended to be used as a flag to mark whether or not a given class/type/object
47 * is internally synchronized, or not.
48 *
49 * It is typically provided as an optional argument to static New () methods, such as
50 * MemoryStream<>::New ()
51 *
52 * \note something marked eNotKnownInternallySynchronized - may in fact be internally synchronized.
53 */
55 eInternallySynchronized,
56 eNotKnownInternallySynchronized
57 };
58 constexpr InternallySynchronized eInternallySynchronized = InternallySynchronized::eInternallySynchronized;
59 constexpr InternallySynchronized eNotKnownInternallySynchronized = InternallySynchronized::eNotKnownInternallySynchronized;
60
61 /**
62 * MUTEX:
63 * We chose to make the default MUTEX recursive_mutex - since most patterns of use will be supported
64 * by this safely.
65 *
66 * To use timed-locks, use timed_recursive_mutex.
67 *
68 * If recursion is not necessary, and for highest performance, SpinLock will often work best.
69 *
70 * \par Example Usage
71 * \code
72 * Synchronized<String> n; // SAME
73 * Synchronized<String,Synchronized_Traits<>> n; // SAME
74 * Synchronized<String,Synchronized_Traits<recursive_mutex>> n; // SAME
75 * \endcode
76 *
77 * or slightly faster, but possibly slower or less safe (depending on usage)
78 * Synchronized<String,Synchronized_Traits<SpinLock>> n;
79 *
80 * \note Use of SUPPORTS_SHARED_LOCKS has HIGH PERFORMANCE OVERHEAD, and only makes sense when you have
81 * read locks held for a long time (and multiple threads doing so).
82 *
83 * @see http://joeduffyblog.com/2009/02/11/readerwriter-locks-and-their-lack-of-applicability-to-finegrained-synchronization/
84 *
85 * \note To change the value of one of the constexpr or type members of Synchronized_Traits, use template
86 * specialization (or pass a completely different traits object to Synchronized<>).
87 *
88 */
89 template <typename MUTEX = recursive_mutex>
91 using MutexType = MUTEX;
92
93 /**
94 * Used internally for assertions that the synchronized object is used safely. If you mean to use differently, specialize
95 */
96 static constexpr bool kIsRecursiveReadMutex = same_as<MUTEX, recursive_mutex> or same_as<MUTEX, recursive_timed_mutex> or
97 same_as<MUTEX, shared_timed_mutex> or same_as<MUTEX, shared_mutex>;
98
99 /**
100 * Used internally for assertions that the synchronized object is used safely. If you mean to use differently, specialize
101 */
102 static constexpr bool kIsRecursiveLockMutex = same_as<MUTEX, recursive_mutex> or same_as<MUTEX, recursive_timed_mutex>;
103
104 /**
105 * If enabled, fTraceLocksName field available, and if its set, DbgTrace calls on lock/unlock.
106 */
107 static constexpr bool kDbgTraceLockUnlockIfNameSet = qStroika_Foundation_Debug_DefaultTracingOn;
108
109 /**
110 */
111 static constexpr bool kSupportsTimedLocks =
112 same_as<MUTEX, shared_timed_mutex> or same_as<MUTEX, recursive_timed_mutex> or same_as<MUTEX, timed_mutex>;
113
114 /**
115 */
116 static constexpr bool kSupportSharedLocks = same_as<MUTEX, shared_timed_mutex> or same_as<MUTEX, shared_mutex>;
117
118 /**
119 */
120 using ReadLockType = conditional_t<kSupportSharedLocks, shared_lock<MUTEX>, unique_lock<MUTEX>>;
121
122 /**
123 */
124 using WriteLockType = unique_lock<MUTEX>;
125 };
126
127 namespace Private_ {
128 struct DbgTraceNameObj_ {
129 // avoid use of Stroika String, due to mutual dependencies - this is low level code
130 optional<std::wstring> fDbgTraceLocksName;
131 };
132 }
133
134 /**
135 */
136 template <typename MUTEX>
137 struct Tracing_Synchronized_Traits : Synchronized_Traits<MUTEX> {
138 static constexpr bool kDbgTraceLockUnlockIfNameSet = true;
139 };
140
141 /**
142 * \brief Wrap any object with Synchronized<> and it can be used similarly to the base type,
143 * but safely in a thread safe manner, from multiple threads. This is similar to std::atomic.
144 *
145 * The idea behind any of these synchronized classes is that they can be used freely from
146 * different threads without worry of data corruption. It is almost as if each operation were
147 * preceeded with a mutex lock (on that object) and followed by an unlock.
148 *
149 * If one thread does a Read operation on Synchronized<T> while another does a write (modification)
150 * operation on Synchronized<T>, the Read will always return a consistent reasonable value, from
151 * before the modification or afterwards, but never a distorted invalid value.
152 *
153 * This is very much closely logically the java 'synchronized' attribute, except that its
154 * not a language extension/attribute here, but rather a class wrapper. Its also implemented
155 * in the library, not the programming language.
156 *
157 * \note LIKE JAVA SYNCHRONIZED
158 * This is SIMPLE to use like the Java (and .net) synchronized attribute(lock) mechanism.
159 * But why does it not suffer from the same performance defect?
160 *
161 * Because with Java - you mix up exceptions and assertions. With Stroika, we have builtin
162 * checking for races (Debug::AssertExternallySynchronizedMutex) in most objects, so
163 * you only use Synchronized<> (or some other more performant mechanism) in the few places
164 * you need it.
165 *
166 * \note Synchronized<> is similar to std::atomic, except that
167 * * You can use it as a mutex with lock_guard and lock for an extended period of time.
168 * * This supports read/write locks.
169 * * This supports locking objects and updated bits of them - not just replacing all at one go
170 *
171 * \par Example Usage
172 * \code
173 * Synchronized<String> n; // SAME
174 * Synchronized<String,Synchronized_Traits<>> n; // SAME
175 * Synchronized<String,Synchronized_Traits<recursive_mutex>> n; // SAME
176 * \endcode
177 *
178 * or slightly faster, but possibly slower or less safe (depending on usage)
179 * \code
180 * Synchronized<String,Synchronized_Traits<SpinLock>> n;
181 * \endcode
182 *
183 * or to allow timed locks
184 * \code
185 * Synchronized<String,Synchronized_Traits<timed_recursive_mutex>> n;
186 * \endcode
187 *
188 * or to read-modify-update in place
189 * \code
190 * //nb: lock lasts til end of enclosing scope
191 * auto lockedConfigData = fConfig_.rwget ();
192 * lockedConfigData->SomeValueChanged = 1;
193 * \endcode
194 *
195 * or to read-modify-update with explicit temporary
196 * \code
197 * //nb: lock lasts til end of enclosing scope
198 * auto lockedConfigData = fConfig_.rwget ();
199 * T value = lockedConfigData;
200 * value.SomeValueChanged = 1;
201 * lockedConfigData.store (value);
202 * \endcode
203 *
204 * or, because Synchronized<> has lock/unlock methods, it can be used with a lock_guard (if associated mutex recursive), as in:
205 * \code
206 * auto&& critSec = lock_guard{n};
207 * // lock to make sure someone else doesn't change n after we made sure it looked good
208 * if (looks_good (n)) {
209 * String a = n;
210 * a = a + a;
211 * n.store (a);
212 * }
213 * \endcode
214 *
215 * \note We consider supporting operator-> for Synchronized<> - and overloading on const to see if we use a Read Lock or a Write lock.
216 * The problem is - that IF its called through a non-const object, it will select the non-const (write lock) even though all that
217 * was needed was the read lock! So this paradigm - though more terse and clear - just encourages inefficient coding (so we
218 * have no read locks - all locks write locks).
219 *
220 * So ONLY support operator-> const overload (brevity and more common than for write). To write - use rwget().
221 *
222 * \note Upgrading a shared_lock to a full lock
223 * We experimented with using boost upgrade_lock code to allow for a full upgrade capability, but this intrinsically
224 * can (easily) yield deadlocks (e.g. thread A holds read lock and tries to upgrade, while thread B holds shared_lock
225 * and waits on something from thread A), and so I decided to abandon this approach.
226 *
227 * Instead, just have upgradeLock release the shared_lock, and re-acquire the mutex as a full lock. BUT - this has problems
228 * too. Typically - you compute something with the shared_lock and notice you want to commit a change, and so upgrade to get
229 * the full lock. But when you do the upgrade, someone else could sneak in and do the same thing invalidating your earlier
230 * computation.
231 *
232 * So - the Upgrade lock APIS have the word "NON_ATOMICALLY" in the name to emphasize this issue, and either return a boolean
233 * indicating failure, or take a callback that gets notified of the need to recompute the cached value/data.
234 *
235 *
236 * \note <a href="Design-Overview.md#Comparisons">Comparisons</a>:
237 * o static_assert (equality_comparable<T> and TRAITS::kIsRecursiveReadMutex ==> equality_comparable<Synchronized<T>>);
238 * o static_assert (totally_ordered<T> and TRAITS::kIsRecursiveReadMutex ==> totally_ordered<Synchronized<T>>);
239 *
240 */
241 template <typename T, typename TRAITS = Synchronized_Traits<>>
242 class Synchronized : public conditional_t<TRAITS::kDbgTraceLockUnlockIfNameSet, Private_::DbgTraceNameObj_, Common::Empty> {
243 public:
244 using element_type = T;
245
246 public:
247 using MutexType = typename TRAITS::MutexType;
248
249 public:
250 class ReadableReference;
251
252 public:
253 class WritableReference;
254
255 public:
256 /**
257 * Create a Synchronized with any argument type the underlying type will be constructed with the same
258 * (perfectly) forwarded arguments.
259 *
260 * And plain copy constructor.
261 *
262 * FOR NOW, avoid MOVE constructor (cuz synchronized is a combination of original data with lock, and not sure what moving
263 * the lock means).
264 */
265 template <typename... ARGUMENT_TYPES>
266 Synchronized (ARGUMENT_TYPES&&... args)
267 requires (constructible_from<T, ARGUMENT_TYPES...>);
268 Synchronized (const Synchronized& src);
269
270 public:
271 /**
272 */
273 nonvirtual Synchronized& operator= (const Synchronized& rhs);
274 nonvirtual Synchronized& operator= (T&& rhs);
275 nonvirtual Synchronized& operator= (const T& rhs);
276
277 public:
278 /**
279 * Synonym for load ()
280 *
281 * \note Do with non-explicit. This makes usage more terse
282 * for cases like:
283 * Synchronized<T> sX_;
284 * T Accessor () { return sX_; }
285 *
286 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
287 * feature (say with RWSynchronized) - use cget ().load ();
288 * The reason this is only defined for recursive mutexes is so that it can be used in a context where this thread
289 * already has a lock (e.g. called rwget ()).
290 */
291 nonvirtual operator T () const
292 requires (TRAITS::kIsRecursiveReadMutex);
293
294 public:
295 /**
296 * Note - unlike operator->, load () returns a copy of the internal data, and only locks while fetching it, so that the
297 * lock does not persist while using the result.
298 *
299 * \note Using load () can be most efficient (least lock contention) with read/write locks (@see RWSynchronized),
300 * since it just uses a read lock and releases it immediately.
301 *
302 * \par Example Usage
303 * \code
304 * Synchronized<Thread::Ptr> sharedData;
305 * sharedData.load ().AbortAndWaitTilDone (); // copies Thread::Ptr and doesn't maintain lock during wait
306 * sharedData->AbortAndWaitTilDone (); // works off internal copy of thread object, and maintains the lock while accessing
307 * \endcode
308 *
309 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
310 * feature (e.g. with RWSynchronized<T>) - use cget ().load (), or existingLock.load ();
311 * The reason this is only defined for recursive mutexes is so that it can be used in a context where this thread
312 * already has a lock (e.g. called rwget ()).
313 */
314 nonvirtual T load () const
315 requires (TRAITS::kIsRecursiveReadMutex);
316 nonvirtual T load (const chrono::duration<double>& tryFor) const
317 requires (TRAITS::kIsRecursiveReadMutex and TRAITS::kSupportsTimedLocks);
318
319 public:
320 /**
321 * @see load ()
322 *
323 * Save the given value into this synchronized object, acquiring the needed write lock first.
324 *
325 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
326 * feature (say with RWSynchronized) - use rwget ().store ();
327 * The reason this is only defined for recursive mutexes is so that it can be used in a context where this thread
328 * already has a lock (e.g. called rwget ()).
329 */
330 nonvirtual void store (const T& v)
331 requires (TRAITS::kIsRecursiveLockMutex);
332 nonvirtual void store (T&& v)
333 requires (TRAITS::kIsRecursiveLockMutex);
334 nonvirtual void store (const T& v, const chrono::duration<double>& tryFor)
335 requires (TRAITS::kIsRecursiveLockMutex and TRAITS::kSupportsTimedLocks);
336 nonvirtual void store (T&& v, const chrono::duration<double>& tryFor)
337 requires (TRAITS::kIsRecursiveLockMutex and TRAITS::kSupportsTimedLocks);
338
339 public:
340 /**
341 * \brief get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole
342 * time the return (often temporary) ReadableReference exists.
343 *
344 * \note this supports multiple readers/single writer, iff the mutex used with Synchronized<> supports it (@see Synchronized)
345 *
346 * \par Example Usage
347 * \code
348 * auto lockedConfigData = fConfig_.cget ();
349 * fCurrentCell_ = lockedConfigData->fCell.Value (Cell::Short);
350 * fCurrentPressure_ = lockedConfigData->fPressure.Value (Pressure::Low);
351 * \endcode
352 *
353 * This is roughly equivalent (if using a recursive mutex) to (COUNTER_EXAMPLE):
354 * \code
355 * lock_guard<Synchronized<T,TRAITS>> critSec{fConfig_};
356 * fCurrentCell_ = fConfig_->fCell.Value (Cell::Short);
357 * fCurrentPressure_ = fConfig_->fPressure.Value (Pressure::Low);
358 * \endcode
359 *
360 * NOTE - THIS EXAMPLE USAGE IS UNSAFE - DONT DO!
361 * \code
362 * auto danglingCRef = fConfig_.cget ().cref(); // the cref() is reference is only valid til the end of the full expression
363 * use (danglingCRef); // BAD cref dangles after end of previous expression;
364 * \endcode
365 *
366 * Except that this works whether using a shared_mutex or regular mutex. Also - this provides only read-only access
367 * (use rwget for read-write access).
368 *
369 * \note - this creates a lock, so be sure TRAITS::kIsRecursiveReadMutex if using this in a place where the same thread may have a lock.
370 */
371 nonvirtual ReadableReference cget () const;
372 nonvirtual ReadableReference cget (const chrono::duration<double>& tryFor) const
373 requires (TRAITS::kSupportsTimedLocks);
374
375 public:
376 /**
377 * \brief get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the whole
378 * time the (often temporary) WritableReference exists.
379 *
380 * \note - this creates a lock, so be sure TRAITS::kIsRecursiveLockMutex if using this in a place where the same thread may have a lock.
381 */
382 nonvirtual WritableReference rwget ();
383 nonvirtual WritableReference rwget (const chrono::duration<double>& tryFor)
384 requires (TRAITS::kSupportsTimedLocks);
385
386 public:
387 /*
388 * \brief alias for cget ()
389 *
390 * \note Considered deprecating, due to confusion between the const and non-const overload of operator->. That was confusing
391 * because the overload was not based on need, but based on the constness of the underlying object.
392 *
393 * But just having operator-> always return a const const ReadableReference is just convenient. Don't use it if
394 * you don't like it, but it adds to clarity.
395 *
396 * And for the more rare 'write locks' - you are forced to use rwget ().
397 *
398 * @see load ()
399 */
400 nonvirtual ReadableReference operator->() const;
401
402 public:
403 /**
404 * \note lock_shared () only works for 'recursive' mutexes which supported 'shared lock'. To avoid the absence of this
405 * feature (say with RWSynchronized) - use rwget () or cget ();
406 *
407 * \note - This is only usable with TRAITS::kIsRecursiveReadMutex, because there would be no way to access the underlying value
408 * otherwise.
409 */
410 nonvirtual void lock_shared () const
411 requires (TRAITS::kIsRecursiveReadMutex and TRAITS::kSupportSharedLocks);
412
413 public:
414 /**
415 * \note unlock_shared () only works for 'recursive' mutexes which supported 'shared lock'. To avoid the absence of this
416 * feature (say with RWSynchronized) - use rwget () or cget ();
417 *
418 * \note - This is only usable with TRAITS::kIsRecursiveReadMutex, because there would be no way to access the underlying value
419 * otherwise.
420 */
421 nonvirtual void unlock_shared () const
422 requires (TRAITS::kIsRecursiveReadMutex and TRAITS::kSupportSharedLocks);
423
424 public:
425 /**
426 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
427 * feature (e.g. with RWSynchronized<T>) - use cget (), or rwget ().
428 *
429 * \note - This is only usable with TRAITS::kIsRecursiveLockMutex, because there would be no way to access the underlying value
430 * otherwise.
431 */
432 nonvirtual void lock () const
433 requires (TRAITS::kIsRecursiveReadMutex);
434
435 public:
436 /**
437 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
438 * feature (e.g. with RWSynchronized<T>) - use cget (), or rwget ().
439 *
440 * \note - This is only usable with TRAITS::kIsRecursiveLockMutex, because there would be no way to access the underlying value
441 * otherwise.
442 */
443 nonvirtual bool try_lock () const
444 requires (TRAITS::kIsRecursiveReadMutex);
445
446 public:
447 /**
448 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
449 * feature (e.g. with RWSynchronized<T>) - use cget (), or rwget ().
450 *
451 * \note - This is only usable with TRAITS::kIsRecursiveLockMutex, because there would be no way to access the underlying value
452 * otherwise.
453 */
454 nonvirtual bool try_lock_for (const chrono::duration<double>& tryFor) const
455 requires (TRAITS::kIsRecursiveReadMutex and TRAITS::kSupportsTimedLocks);
456
457 public:
458 /**
459 * \note This works only for 'recursive' mutexes (the default, except for RWSynchronized). To avoid the absence of this
460 * feature (e.g. with RWSynchronized<T>) - use cget (), or rwget ().
461 *
462 * \note - This is only usable with TRAITS::kIsRecursiveLockMutex, because there would be no way to access the underlying value
463 * otherwise.
464 */
465 nonvirtual void unlock () const
466 requires (TRAITS::kIsRecursiveReadMutex);
467
468 public:
469 /**
470 */
471 nonvirtual bool operator== (const Synchronized& rhs) const
472 requires (TRAITS::kIsRecursiveReadMutex and equality_comparable<T>);
473 nonvirtual bool operator== (const T& rhs) const
474 requires (TRAITS::kIsRecursiveReadMutex and equality_comparable<T>);
475
476 public:
477 /**
478 */
479 nonvirtual auto operator<=> (const Synchronized& rhs) const
480 requires (TRAITS::kIsRecursiveReadMutex and three_way_comparable<T>);
481 nonvirtual auto operator<=> (const T& rhs) const
482 requires (TRAITS::kIsRecursiveReadMutex and three_way_comparable<T>);
483
484 public:
485 /**
486 * \brief Upgrade a shared_lock (ReadableReference) to a (WritableReference) full lock temporarily in the context of argument function; return true if succeeds, and false if fails (timeout trying to full-lock) or argument doWithWriteLock () returns false
487 *
488 * @see UpgradeLockNonAtomically - to just calls UpgradeLockNonAtomicallyQuietly () and throws timeout on timeout intervening WriteLock or doWithWriteLock returns false
489 *
490 * A DEFEFCT with (this) UpgradeLockNonAtomically API, is that you cannot count on values computed with the read lock to remain
491 * valid in the upgrade lock (since we unlock and then re-lock). We resolve this by having two versions of UpgradeLockNonAtomically,
492 * one where the callback gets notified there was an intervening writelock, and one where the entire call fails and you have to
493 * re-run.
494 *
495 * \note optional 'bool interveningWriteLock' parameter - if present, intervening locks are flagged with this parameter, and if
496 * the parameter is NOT present, intervening locks are treated as timeouts (even if infinite timeout specified)
497 *
498 * \note - also returns false on intervening lock IFF doWithWriteLock/1 passed in has no intervening WriteLock parameter.
499 *
500 * \note optional 'bool interveningWriteLock' parameter - if present, intervening locks are flagged with this parameter, and if
501 * the parameter is NOT present, intervening locks are treated as timeouts (even if infinite timeout specified)
502 *
503 * \note - This does NOT require the mutex be recursive - just supporting both lock_shared and lock ()
504 *
505 * \note - This function takes as argument an existing ReadableReference, which MUST come from a cget on this Synchronized object
506 * (and therefore must be locked) and DURING the context of this function call that becomes invalid, but when this call returns
507 * it will still be locked READONLY. This does NOT change the lock to writable (after the call) - but ONLY during the call
508 * of the argument function.
509 *
510 * \par Example Usage
511 * \code
512 * Execution::RWSynchronized<Status_> fStatus_;
513 * ...
514 * again:
515 * auto lockedStatus = fStatus_.cget ();
516 * // do a bunch of code that only needs read access
517 * if (some rare event) {
518 * if (not fStatus_.UpgradeLockNonAtomicallyQuietly (&lockedStatus, [=](auto&& writeLock) {
519 * writeLock.rwref ().fCompletedScans.Add (scan);
520 * }
521 * )) {
522 * Execution::Sleep (1s);
523 * goto again; // important to goto before we acquire readlock to avoid deadlock when multiple threads do this
524 * }
525 * }
526 * \endcode
527 */
528 nonvirtual bool UpgradeLockNonAtomicallyQuietly (ReadableReference* lockBeingUpgraded, const function<void (WritableReference&&)>& doWithWriteLock,
529 Time::DurationSeconds timeout = Time::kInfinity)
530 requires (TRAITS::kSupportSharedLocks and TRAITS::kSupportsTimedLocks);
531 nonvirtual bool UpgradeLockNonAtomicallyQuietly (ReadableReference* lockBeingUpgraded,
532 const function<bool (WritableReference&&, bool interveningWriteLock)>& doWithWriteLock,
533 Time::DurationSeconds timeout = Time::kInfinity)
534 requires (TRAITS::kSupportSharedLocks and TRAITS::kSupportsTimedLocks);
535 ;
536
537 public:
538 /**
539 * \brief Same as UpgradeLockNonAtomicallyQuietly, but throws on failure (either timeout or if argument function returns false)
540 *
541 * \note - the 'ReadableReference' must be shared_locked coming in, and will be identically shared_locked on return.
542 *
543 * \note - throws on timeout OR if interveningWriteLock and doWithWriteLock/1 passed, or if doWithWriteLock returns false
544 *
545 * \note - This does NOT require the mutex be recursive - just supporting both lock_shared and lock ()
546 *
547 * \note - the timeout refers ONLY the acquiring the upgrade - not the time it takes to re-acquire the shared lock or perform
548 * the argument operation
549 *
550 * \par Example Usage
551 * \code
552 * Execution::RWSynchronized<Status_> fStatus_;
553 * auto lockedStatus = fStatus_.cget ();
554 * // do a bunch of code that only needs read access
555 * if (some rare event) {
556 * // This COULD fail/throw only if called from intervening lock
557 * fStatus_.UpgradeLockNonAtomically ([=](auto&& writeLock) {
558 * writeLock.rwref ().fCompletedScans.Add (scan);
559 * });
560 * }
561 * \endcode
562 */
563 nonvirtual void UpgradeLockNonAtomically (ReadableReference* lockBeingUpgraded, const function<void (WritableReference&&)>& doWithWriteLock,
564 Time::DurationSeconds timeout = Time::kInfinity)
565 requires (TRAITS::kSupportSharedLocks and TRAITS::kSupportsTimedLocks);
566 nonvirtual void UpgradeLockNonAtomically (ReadableReference* lockBeingUpgraded,
567 const function<bool (WritableReference&&, bool interveningWriteLock)>& doWithWriteLock,
568 Time::DurationSeconds timeout = Time::kInfinity)
569 requires (TRAITS::kSupportSharedLocks and TRAITS::kSupportsTimedLocks);
570 ;
571
572 private:
573 nonvirtual void NoteLockStateChanged_ (const wchar_t* m) const noexcept;
574
575 private:
576 using ReadLockType_ = typename TRAITS::ReadLockType;
577
578 private:
579 using WriteLockType_ = typename TRAITS::WriteLockType;
580
581 private:
582 T fProtectedValue_;
583 mutable MutexType fMutex_;
584 /*
585 * note: we don't need to use atomic/or extra lock for updating this because
586 * its ONLY INCREMENETED and EXAMINED when fMutex_ is owned.
587 */
588 mutable unsigned int fWriteLockCount_{0};
589 };
590
591 /**
592 * Anything that constructs a ReadableReference - for example - Synchronized<T, TRAITS>::cget () - is suitable for multiple readers at the same time,
593 * so long as using Synchronized<> of a type that supports shared locks (@see RWSynchronized<>)
594 */
595 template <typename T, typename TRAITS>
597 : public conditional_t<TRAITS::kDbgTraceLockUnlockIfNameSet, Private_::DbgTraceNameObj_, Common::Empty> {
598 protected:
599 /**
600 * If specified, either subclass, or external lock used for lifetime of this object. So this class
601 * just provides access, but doesn't actually do a lock.
602 */
603 enum class _ExternallyLocked {
604 _eExternallyLocked
605 };
606
607 protected:
608 /**
609 * Can construct ReadableReference with nullptr_t mutex, in which case its the subclasses responsibility to manage locking
610 *
611 * \pre t != nullptr, and this class holds onto that pointer.
612 */
613 ReadableReference (const Synchronized* s);
614 ReadableReference (const Synchronized* s, _ExternallyLocked);
615 ReadableReference (const Synchronized* s, ReadLockType_&& readLock);
616
617 public:
618 ReadableReference (const ReadableReference& src) = delete; // must move because both src and dest cannot have the unique lock
619
620 public:
621 ReadableReference (ReadableReference&& src);
622
623 public:
624 nonvirtual const ReadableReference& operator= (const ReadableReference& rhs) = delete;
625
626 public:
627 ~ReadableReference ();
628
629 public:
630 /**
631 * \note Considered losing operator-> here as possibly confusing (e.g. when mixed with Synchronized<optional<xxx>>>).
632 * But you don't need to use it, and this really does act as a smart pointer so it should most often just be
633 * more clear.
634 */
635 nonvirtual const T* operator->() const;
636
637 public:
638 /**
639 */
640 nonvirtual const T& cref () const;
641
642 public:
643 /**
644 * \brief alias for cref () - but allows often simpler short-hand. Use explicit .cref () if you run into ambiguities.
645 *
646 * \note OK to return const reference here because we own a lock anyhow
647 */
648 nonvirtual operator const T& () const;
649
650 public:
651 /**
652 * \brief more or less identical to cref () - except return value is value, not reference.
653 */
654 nonvirtual T load () const;
655
656 protected:
657 const T* fT;
658
659 protected:
660 nonvirtual void _NoteLockStateChanged (const wchar_t* m) const;
661
662 private:
663 ReadLockType_ fSharedLock_{}; // MAYBE unused if actual readlock held elsewhere, like in subclass
664
665 private:
666 friend class Synchronized;
667 };
668
669 /**
670 */
671 template <typename T, typename TRAITS>
672 class Synchronized<T, TRAITS>::WritableReference : public Synchronized<T, TRAITS>::ReadableReference {
673 private:
674 using inherited = typename Synchronized<T, TRAITS>::ReadableReference;
675
676 protected:
677 /**
678 */
679 WritableReference (Synchronized* s);
680 WritableReference (Synchronized* s, WriteLockType_&& writeLock);
681 WritableReference (Synchronized* s, Time::DurationSeconds timeout);
682 WritableReference (const WritableReference& src) = delete; // must move because both src and dest cannot have the unique lock
683
684 public:
685 WritableReference (WritableReference&& src);
686 nonvirtual WritableReference& operator= (const WritableReference& rhs) = delete;
687 nonvirtual const WritableReference& operator= (T rhs);
688
689 public:
690 /**
691 * \note Considered losing operator-> here as possibly confusing (e.g. when mixed with Synchronized<optional<xxx>>>).
692 * But you don't need to use it, and this really does act as a smart pointer so it should most often just be
693 * more clear.
694 */
695 nonvirtual T* operator->();
696 nonvirtual const T* operator->() const;
697
698 public:
699 /**
700 * Get readable and writable reference to the underlying object.
701 *
702 * \note We experimented with overloading just ref() with const and non const versions, but the trouble with this
703 * is that from the caller, its not obvious which version you are getting, and it often matters a lot, so
704 * this appeared - though less convenient - less confusing.
705 *
706 * We can always add (back) a ref () - so overloaded - method.
707 */
708 nonvirtual T& rwref ();
709
710 public:
711 /**
712 */
713 nonvirtual void store (const T& v);
714 nonvirtual void store (T&& v);
715
716 private:
717 WriteLockType_ fWriteLock_; // can be empty if actual lock held elsewhere (like in caller/subclass)
718
719 private:
720 friend class Synchronized;
721 };
722
723 /**
724 */
725 template <typename T, typename TRAITS>
726 auto operator^ (const Synchronized<T, TRAITS>& lhs, T rhs) -> decltype (T{} ^ T{});
727 template <typename T, typename TRAITS>
728 auto operator^ (T lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} ^ T{});
729 template <typename T, typename TRAITS>
730 auto operator^ (const Synchronized<T, TRAITS>& lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} ^ T{});
731
732 /**
733 */
734 template <typename T, typename TRAITS>
735 auto operator* (const Synchronized<T, TRAITS>& lhs, T rhs) -> decltype (T{} * T{});
736 template <typename T, typename TRAITS>
737 auto operator* (T lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} * T{});
738 template <typename T, typename TRAITS>
739 auto operator* (const Synchronized<T, TRAITS>& lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} * T{});
740
741 /**
742 */
743 template <typename T, typename TRAITS>
744 auto operator+ (const Synchronized<T, TRAITS>& lhs, T rhs) -> decltype (T{} + T{});
745 template <typename T, typename TRAITS>
746 auto operator+ (T lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} + T{});
747 template <typename T, typename TRAITS>
748 auto operator+ (const Synchronized<T, TRAITS>& lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} + T{});
749
750 /**
751 */
752 template <typename T, typename TRAITS, typename RHSTYPE>
753 auto operator-= (Synchronized<T, TRAITS>& lhs, RHSTYPE rhs) -> decltype (lhs.rwget ()->operator-= (rhs));
754
755 /**
756 */
757 template <typename T, typename TRAITS, typename RHSTYPE>
758 auto operator+= (Synchronized<T, TRAITS>& lhs, RHSTYPE rhs) -> decltype (lhs.rwget ()->operator+= (rhs));
759
760 /**
761 */
762 template <typename T, typename TRAITS>
763 auto operator- (const Synchronized<T, TRAITS>& lhs, T rhs) -> decltype (T{} - T{});
764 template <typename T, typename TRAITS>
765 auto operator- (T lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} - T{});
766 template <typename T, typename TRAITS>
767 auto operator- (const Synchronized<T, TRAITS>& lhs, const Synchronized<T, TRAITS>& rhs) -> decltype (T{} - T{});
768
769 /**
770 * QuickSynchronized will always use a mutex which is quick, and not a cancelation point. It will typically be
771 * implemented using a std::mutex, or a SpinLock, whichever is faster. So - don't use this where you hold
772 * onto the lock for extended periods ;-).
773 */
774 template <typename T>
776 conditional_t<kSpinLock_IsFasterThan_mutex, Synchronized<T, Synchronized_Traits<SpinLock>>, Synchronized<T, Synchronized_Traits<mutex>>>;
777
778 /**
779 * \note - TimedSynchronized doesn't do shared_locks - just a single mutex lock (that is recursive, and allows timeouts)
780 */
781 template <typename T>
783
784 /**
785 * RWSynchronized will always use some sort of mutex which supports multiple readers, and a single writer.
786 * Typically, using shared_mutex (or shared_timed_mutex).
787 *
788 * \note Use of RWSynchronized has HIGH PERFORMANCE OVERHEAD, and only makes sense when you have
789 * read locks held for a long time (and multiple threads doing so)
790 */
791 template <typename T>
793
794}
795
796/*
797 ********************************************************************************
798 ***************************** Implementation Details ***************************
799 ********************************************************************************
800 */
801#include "Synchronized.inl"
802
803#endif /*_Stroika_Foundation_Execution_Synchronized_h_*/
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
_ExternallyLocked
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
nonvirtual WritableReference rwget()
get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the who...
nonvirtual void UpgradeLockNonAtomically(ReadableReference *lockBeingUpgraded, const function< void(WritableReference &&)> &doWithWriteLock, Time::DurationSeconds timeout=Time::kInfinity)
Same as UpgradeLockNonAtomicallyQuietly, but throws on failure (either timeout or if argument functio...
nonvirtual bool UpgradeLockNonAtomicallyQuietly(ReadableReference *lockBeingUpgraded, const function< void(WritableReference &&)> &doWithWriteLock, Time::DurationSeconds timeout=Time::kInfinity)
Upgrade a shared_lock (ReadableReference) to a (WritableReference) full lock temporarily in the conte...
nonvirtual bool try_lock_for(const chrono::duration< double > &tryFor) const
nonvirtual ReadableReference cget() const
get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole...
conditional_t< kSpinLock_IsFasterThan_mutex, Synchronized< T, Synchronized_Traits< SpinLock > >, Synchronized< T, Synchronized_Traits< mutex > > > QuickSynchronized