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