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