EtcPal  HEAD (unstable)
ETC Platform Abstraction Layer (EtcPal)
View other versions:
synchronized_value.h
Go to the documentation of this file.
1 /******************************************************************************
2  * Copyright 2026 ETC Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *******************************************************************************
16  * This file is a part of EtcPal. For more information, go to:
17  * https://github.com/ETCLabs/EtcPal
18  ******************************************************************************/
19 
27 
30 
31 #ifndef ETCPAL_CPP_SYNCHRONIZED_VALUE_H_
32 #define ETCPAL_CPP_SYNCHRONIZED_VALUE_H_
33 
34 #include <memory>
35 #include <stdexcept>
36 #include <type_traits>
37 #include <utility>
38 
39 #include "etcpal/cpp/common.h"
40 #include "etcpal/cpp/mutex.h"
42 
43 namespace etcpal
44 {
82 
83 template <typename T, typename LockType = Mutex>
84 class SynchronizedValue;
85 template <typename T, typename LockType = Mutex>
86 class ConstUniqueLockPtr;
87 template <typename T, typename LockType = Mutex>
88 class UniqueLockPtr;
89 
90 namespace detail
91 {
93 template <typename...>
94 struct FirstType
95 {
96  using type = void; // NOLINT(readability-identifier-naming)
97 };
98 template <typename First, typename... Rest>
99 struct FirstType<First, Rest...>
100 {
101  using type = First; // NOLINT(readability-identifier-naming)
102 };
103 } // namespace detail
104 
108 {
109 };
114 {
115 };
120 {
121 };
122 
129 template <typename LockType>
130 struct SupportsTimedLock : std::false_type
131 {
132 };
133 template <>
134 struct SupportsTimedLock<Mutex> : std::integral_constant<bool, ETCPAL_MUTEX_HAS_TIMED_LOCK>
135 {
136 };
137 template <>
138 struct SupportsTimedLock<RecursiveMutex> : std::integral_constant<bool, ETCPAL_RECURSIVE_MUTEX_HAS_TIMED_LOCK>
139 {
140 };
141 
152 template <typename T, typename LockType = Mutex>
154 {
155 public:
158  explicit ConstStrictLockPtr(const SynchronizedValue<T, LockType>& sync) : sync_(&sync)
159  {
160  if (!sync_->lock_.Lock())
161  ETCPAL_THROW(std::runtime_error("etcpal SynchronizedValue lock failed."));
162  }
163 
165  ConstStrictLockPtr(const SynchronizedValue<T, LockType>& sync, AdoptLockTag /*unused*/) noexcept : sync_(&sync) {}
166 
169  explicit ConstStrictLockPtr(ConstUniqueLockPtr<T, LockType>&& other) : sync_(other.sync_)
170  {
171  if (!other.owns_)
172  ETCPAL_THROW(std::runtime_error("etcpal ConstStrictLockPtr constructed from a non-owning handle."));
173  other.sync_ = nullptr;
174  other.owns_ = false;
175  }
176 
177  ConstStrictLockPtr(const ConstStrictLockPtr&) = delete;
178  ConstStrictLockPtr& operator=(const ConstStrictLockPtr&) = delete;
179  ConstStrictLockPtr& operator=(ConstStrictLockPtr&&) = delete;
180 
182  ConstStrictLockPtr(ConstStrictLockPtr&& other) noexcept : sync_(other.sync_) { other.sync_ = nullptr; }
183 
186  {
187  if (sync_)
188  sync_->lock_.Unlock();
189  }
190 
192  const T& operator*() const noexcept { return sync_->value_; }
194  const T* operator->() const noexcept { return std::addressof(sync_->value_); }
195 
196 protected:
197  // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) - derived handles need direct access
198  const SynchronizedValue<T, LockType>* sync_;
199 };
200 
210 template <typename T, typename LockType = Mutex>
211 class StrictLockPtr : public ConstStrictLockPtr<T, LockType>
212 {
213 public:
216  explicit StrictLockPtr(SynchronizedValue<T, LockType>& sync) : ConstStrictLockPtr<T, LockType>(sync) {}
217 
221  {
222  }
223 
226  explicit StrictLockPtr(UniqueLockPtr<T, LockType>&& other) : ConstStrictLockPtr<T, LockType>(std::move(other)) {}
227 
230 
231  // The casts below are sound: mutable handles can only be constructed from a non-const SynchronizedValue.
233  T& operator*() noexcept
234  {
235  return const_cast<T&>(this->sync_->value_); // NOLINT(cppcoreguidelines-pro-type-const-cast)
236  }
238  T* operator->() noexcept
239  {
240  return const_cast<T*>(std::addressof(this->sync_->value_)); // NOLINT(cppcoreguidelines-pro-type-const-cast)
241  }
242 };
243 
254 template <typename T, typename LockType>
256 {
257 public:
260  explicit ConstUniqueLockPtr(const SynchronizedValue<T, LockType>& sync) : sync_(&sync), owns_(false) { Lock(); }
261 
264  : sync_(&sync), owns_(true)
265  {
266  }
267 
270  : sync_(&sync), owns_(false)
271  {
272  }
273 
276  ConstUniqueLockPtr(const SynchronizedValue<T, LockType>& sync, TryToLockTag /*unused*/, int timeout_ms = 0) noexcept
277  : sync_(&sync), owns_(sync.TryLockFor(timeout_ms))
278  {
279  }
280 
281  ConstUniqueLockPtr(const ConstUniqueLockPtr&) = delete;
282  ConstUniqueLockPtr& operator=(const ConstUniqueLockPtr&) = delete;
283  ConstUniqueLockPtr& operator=(ConstUniqueLockPtr&&) = delete;
284 
286  ConstUniqueLockPtr(ConstUniqueLockPtr&& other) noexcept : sync_(other.sync_), owns_(other.owns_)
287  {
288  other.sync_ = nullptr;
289  other.owns_ = false;
290  }
291 
293  ~ConstUniqueLockPtr() noexcept { Unlock(); }
294 
296  explicit operator bool() const noexcept { return owns_; }
298  bool OwnsLock() const noexcept { return owns_; }
299 
301  void Unlock() noexcept
302  {
303  if (owns_)
304  {
305  sync_->lock_.Unlock();
306  owns_ = false;
307  }
308  }
309 
312  void Lock()
313  {
314  if (!sync_)
315  ETCPAL_THROW(std::runtime_error("etcpal lock handle does not reference a lock."));
316  if (owns_)
317  ETCPAL_THROW(std::runtime_error("etcpal lock handle already owns the lock."));
318  if (!sync_->lock_.Lock())
319  ETCPAL_THROW(std::runtime_error("etcpal SynchronizedValue lock failed."));
320  owns_ = true;
321  }
322 
332  bool TryLock(int timeout_ms = 0)
333  {
334  if (!sync_)
335  ETCPAL_THROW(std::runtime_error("etcpal lock handle does not reference a lock."));
336  if (owns_)
337  ETCPAL_THROW(std::runtime_error("etcpal lock handle already owns the lock."));
338  owns_ = sync_->TryLockFor(timeout_ms);
339  return owns_;
340  }
341 
343  void Release() noexcept
344  {
345  sync_ = nullptr;
346  owns_ = false;
347  }
348 
351  const T& operator*() const
352  {
353  if (!owns_)
354  ETCPAL_THROW(std::runtime_error("etcpal lock handle does not own the lock."));
355  return sync_->value_;
356  }
359  const T* operator->() const
360  {
361  if (!owns_)
362  ETCPAL_THROW(std::runtime_error("etcpal lock handle does not own the lock."));
363  return std::addressof(sync_->value_);
364  }
365 
366 protected:
367  // NOLINT below: the derived mutable handle and the strict-pointer conversion need direct access.
368  const SynchronizedValue<T, LockType>* sync_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
369  bool owns_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
370 
371  friend class ConstStrictLockPtr<T, LockType>;
372 };
373 
381 template <typename T, typename LockType>
382 class UniqueLockPtr : public ConstUniqueLockPtr<T, LockType>
383 {
384 public:
387  explicit UniqueLockPtr(SynchronizedValue<T, LockType>& sync) : ConstUniqueLockPtr<T, LockType>(sync) {}
388 
392  {
393  }
394 
398  {
399  }
400 
403  UniqueLockPtr(SynchronizedValue<T, LockType>& sync, TryToLockTag /*unused*/, int timeout_ms = 0) noexcept
404  : ConstUniqueLockPtr<T, LockType>(sync, TryToLockTag{}, timeout_ms)
405  {
406  }
407 
408  using ConstUniqueLockPtr<T, LockType>::operator*;
409  using ConstUniqueLockPtr<T, LockType>::operator->;
410 
411  // The casts below are sound: mutable handles can only be constructed from a non-const SynchronizedValue.
415  {
416  if (!this->owns_)
417  ETCPAL_THROW(std::runtime_error("etcpal lock handle does not own the lock."));
418  return const_cast<T&>(this->sync_->value_); // NOLINT(cppcoreguidelines-pro-type-const-cast)
419  }
423  {
424  if (!this->owns_)
425  ETCPAL_THROW(std::runtime_error("etcpal lock handle does not own the lock."));
426  return const_cast<T*>(std::addressof(this->sync_->value_)); // NOLINT(cppcoreguidelines-pro-type-const-cast)
427  }
428 };
429 
441 template <typename T, typename LockType>
443 {
444 public:
451  template <typename... Args,
452  typename = typename std::enable_if<
453  std::is_constructible<T, Args&&...>::value &&
454  !std::is_same<typename std::decay<typename detail::FirstType<Args...>::type>::type,
455  SynchronizedValue>::value>::type>
456  // The noexcept clause assumes LockType's default constructor cannot throw, which holds for the EtcPal
457  // lock types but is not checked (they do not declare it noexcept, so a trait check would always be false).
458  explicit SynchronizedValue(Args&&... args) noexcept(std::is_nothrow_constructible<T, Args...>::value)
459  : value_(std::forward<Args>(args)...)
460  {
461  }
462 
466  T& GetValueUnsafe() noexcept { return value_; }
467  LockType& GetLockUnsafe() noexcept { return lock_; }
469 
473 
480 
488 
500  UniqueLockPtr<T, LockType> TryToSynchronize(int timeout_ms = 0) noexcept
501  {
502  return UniqueLockPtr<T, LockType>(*this, TryToLockTag{}, timeout_ms);
503  }
504  ConstUniqueLockPtr<T, LockType> TryToSynchronize(int timeout_ms = 0) const noexcept
505  {
506  return ConstUniqueLockPtr<T, LockType>(*this, TryToLockTag{}, timeout_ms);
507  }
509 
514 
519 
523  {
525  }
526 
530  {
532  }
534 
544  template <typename F>
545  decltype(auto) Apply(F&& func)
546  {
547  static_assert(!std::is_reference<decltype(std::forward<F>(func)(std::declval<T&>()))>::value,
548  "Apply must not return a reference; it would outlive the lock");
549  auto lock = Synchronize();
550  return std::forward<F>(func)(*lock);
551  }
552  template <typename F>
553  decltype(auto) Apply(F&& func) const
554  {
555  static_assert(!std::is_reference<decltype(std::forward<F>(func)(std::declval<const T&>()))>::value,
556  "Apply must not return a reference; it would outlive the lock");
557  auto lock = Synchronize();
558  return std::forward<F>(func)(*lock);
559  }
561 
562 private:
563  friend class ConstStrictLockPtr<T, LockType>;
564  friend class StrictLockPtr<T, LockType>;
565  friend class ConstUniqueLockPtr<T, LockType>;
566  friend class UniqueLockPtr<T, LockType>;
567 
568  // Honor the timeout only where the platform supports timed locking; otherwise poll, so an unsupported
569  // platform never silently blocks forever (etcpal_*_timed_lock() with a nonzero timeout is Lock() there).
570  bool TryLockFor(int timeout_ms) const noexcept
571  {
572  return SupportsTimedLock<LockType>::value ? lock_.TryLock(timeout_ms) : lock_.TryLock(0);
573  }
574 
575  T value_;
576  mutable LockType lock_;
577 };
578 
579 } // namespace etcpal
580 
581 #endif // ETCPAL_CPP_SYNCHRONIZED_VALUE_H_
An RAII handle holding a SynchronizedValue's lock and granting read-only access to the value.
Definition: synchronized_value.h:154
ConstStrictLockPtr(const SynchronizedValue< T, LockType > &sync, AdoptLockTag) noexcept
Adopt the given SynchronizedValue's lock, which the calling thread must already hold.
Definition: synchronized_value.h:165
ConstStrictLockPtr(ConstStrictLockPtr &&other) noexcept
Move ownership of the held lock from another handle, which is left empty.
Definition: synchronized_value.h:182
const T * operator->() const noexcept
Access a member of the guarded value. Undefined behavior if this handle has been moved from.
Definition: synchronized_value.h:194
~ConstStrictLockPtr() noexcept
Release the lock.
Definition: synchronized_value.h:185
ConstStrictLockPtr(ConstUniqueLockPtr< T, LockType > &&other)
Take over the lock held by other, which is left empty.
Definition: synchronized_value.h:169
const T & operator*() const noexcept
Get a reference to the guarded value. Undefined behavior if this handle has been moved from.
Definition: synchronized_value.h:192
ConstStrictLockPtr(const SynchronizedValue< T, LockType > &sync)
Lock the given SynchronizedValue for read-only access.
Definition: synchronized_value.h:158
An RAII handle for read-only access whose lock may or may not be held.
Definition: synchronized_value.h:256
void Release() noexcept
Disassociate from the lock without unlocking it; the caller becomes responsible for the lock.
Definition: synchronized_value.h:343
ConstUniqueLockPtr(const SynchronizedValue< T, LockType > &sync, DeferLockTag) noexcept
Reference the given SynchronizedValue without acquiring the lock.
Definition: synchronized_value.h:269
const T & operator*() const
Get a reference to the guarded value.
Definition: synchronized_value.h:351
const T * operator->() const
Access a member of the guarded value.
Definition: synchronized_value.h:359
void Unlock() noexcept
Release the lock early, if owned. Does nothing otherwise.
Definition: synchronized_value.h:301
void Lock()
Acquire the lock, blocking until it is available.
Definition: synchronized_value.h:312
bool TryLock(int timeout_ms=0)
Try to acquire the lock, waiting up to timeout_ms.
Definition: synchronized_value.h:332
ConstUniqueLockPtr(ConstUniqueLockPtr &&other) noexcept
Move ownership from another handle, which is left empty.
Definition: synchronized_value.h:286
ConstUniqueLockPtr(const SynchronizedValue< T, LockType > &sync, AdoptLockTag) noexcept
Adopt the given SynchronizedValue's lock, which the calling thread must already hold.
Definition: synchronized_value.h:263
ConstUniqueLockPtr(const SynchronizedValue< T, LockType > &sync)
Lock the given SynchronizedValue for read-only access, blocking until the lock is acquired.
Definition: synchronized_value.h:260
ConstUniqueLockPtr(const SynchronizedValue< T, LockType > &sync, TryToLockTag, int timeout_ms=0) noexcept
Try to lock the given SynchronizedValue, waiting up to timeout_ms; check OwnsLock() for the result.
Definition: synchronized_value.h:276
bool OwnsLock() const noexcept
Whether this handle currently owns the lock.
Definition: synchronized_value.h:298
~ConstUniqueLockPtr() noexcept
Release the lock if owned.
Definition: synchronized_value.h:293
A wrapper class for the EtcPal mutex type.
Definition: mutex.h:88
A wrapper class for the EtcPal recursive mutex type.
Definition: recursive_mutex.h:64
An RAII handle holding a SynchronizedValue's lock and granting mutable access to the value.
Definition: synchronized_value.h:212
StrictLockPtr(UniqueLockPtr< T, LockType > &&other)
Take over the lock held by other, which is left empty.
Definition: synchronized_value.h:226
T & operator*() noexcept
Get a mutable reference to the guarded value. Undefined behavior if this handle has been moved from.
Definition: synchronized_value.h:233
StrictLockPtr(SynchronizedValue< T, LockType > &sync, AdoptLockTag) noexcept
Adopt the given SynchronizedValue's lock, which the calling thread must already hold.
Definition: synchronized_value.h:219
StrictLockPtr(SynchronizedValue< T, LockType > &sync)
Lock the given SynchronizedValue for mutable access.
Definition: synchronized_value.h:216
T * operator->() noexcept
Access a member of the guarded value. Undefined behavior if this handle has been moved from.
Definition: synchronized_value.h:238
A value bundled with a lock that guards all access to it.
Definition: synchronized_value.h:443
UniqueLockPtr< T, LockType > DeferSynchronize() noexcept
Reference the value without locking; acquire later with Lock() or TryLock() on the handle.
Definition: synchronized_value.h:521
UniqueLockPtr< T, LockType > UniqueSynchronize()
Lock the value, returning a handle that can also Unlock(), re-Lock(), or Release() it.
Definition: synchronized_value.h:517
StrictLockPtr< T, LockType > operator->()
Lock the value and access a member of it for the duration of the enclosing expression.
Definition: synchronized_value.h:476
ConstStrictLockPtr< T, LockType > Synchronize() const
Lock the value, returning a handle that holds the lock until it is destroyed.
Definition: synchronized_value.h:486
UniqueLockPtr< T, LockType > AdoptSynchronize() noexcept
Adopt the lock, which the calling thread must already hold, into a handle that will release it.
Definition: synchronized_value.h:528
ConstStrictLockPtr< T, LockType > operator->() const
Lock the value and access a member of it for the duration of the enclosing expression.
Definition: synchronized_value.h:479
StrictLockPtr< T, LockType > Synchronize()
Lock the value, returning a handle that holds the lock until it is destroyed.
Definition: synchronized_value.h:483
SynchronizedValue(Args &&... args) noexcept(std::is_nothrow_constructible< T, Args... >::value)
Construct the guarded value in place, forwarding the given arguments to its constructor.
Definition: synchronized_value.h:458
An RAII handle for mutable access whose lock may or may not be held.
Definition: synchronized_value.h:383
UniqueLockPtr(SynchronizedValue< T, LockType > &sync, TryToLockTag, int timeout_ms=0) noexcept
Try to lock the given SynchronizedValue, waiting up to timeout_ms; check OwnsLock() for the result.
Definition: synchronized_value.h:403
UniqueLockPtr(SynchronizedValue< T, LockType > &sync)
Lock the given SynchronizedValue for mutable access, blocking until the lock is acquired.
Definition: synchronized_value.h:387
UniqueLockPtr(SynchronizedValue< T, LockType > &sync, DeferLockTag) noexcept
Reference the given SynchronizedValue without acquiring the lock.
Definition: synchronized_value.h:396
UniqueLockPtr(SynchronizedValue< T, LockType > &sync, AdoptLockTag) noexcept
Adopt the given SynchronizedValue's lock, which the calling thread must already hold.
Definition: synchronized_value.h:390
T * operator->()
Access a member of the guarded value.
Definition: synchronized_value.h:422
T & operator*()
Get a mutable reference to the guarded value.
Definition: synchronized_value.h:414
Common definitions used by EtcPal C++ wrappers.
C++ wrapper and utilities for etcpal/mutex.h.
C++ wrapper and utilities for etcpal/recursive_mutex.h.
Tag requesting that a lock already held by the calling thread be adopted rather than acquired.
Definition: synchronized_value.h:108
Tag requesting that the lock be left unacquired on construction, to be acquired later with ConstUniqu...
Definition: synchronized_value.h:114
Trait reporting whether SynchronizedValue::TryToSynchronize() honors its timeout for LockType on the ...
Definition: synchronized_value.h:131
Tag requesting that the lock be acquired with a poll or optional timed wait, never failing with an ex...
Definition: synchronized_value.h:120
Meta struct to extract the first type from a parameter pack.
Definition: synchronized_value.h:95