UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
TransactionallySafeSharedMutex.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Async/SharedMutex.h"
6#include "AutoRTFM.h"
8
9#if UE_AUTORTFM
12#endif
13
14namespace UE
15{
16
17#if UE_AUTORTFM
18
19// A transactionally-safe shared lock that works in the following novel ways:
20// - In the open (non-transactional):
21// - Take the lock like before. Simple!
22// - Free the lock like before too.
23// - In the closed (transactional):
24// - During locking we query `TransactionalLockCount`:
25// - 0 means we haven't taken the lock within our transaction nest and need to acquire the lock.
26// - Otherwise we already have the lock (and are preventing non-transactional code seeing any
27// modifications we've made while holding the lock), so just bump `TransactionalLockCount`.
28// - We also register an on-abort handler to release the lock should we abort (but we need to
29// query `TransactionalLockCount` even there because we could be aborting an inner transaction
30// and the parent transaction still wants to have the lock held!).
31// - During unlocking we defer doing the unlock until the transaction commits.
32//
33// Thus with this approach we will hold this lock for the *entirety* of the transactional nest should
34// we take the lock during the transaction, thus preventing non-transactional code from seeing any
35// modifications we should make.
36//
37// If we are within a transaction, we pessimise our shared lock to a full lock. Note: that it should
38// potentially be possible to have shared locks work correctly, but serious care will have to be taken to
39// ensure that we don't have:
40// Open Thread Closed Thread
41// ----------- SharedLock
42// ----------- SharedUnlock
43// Lock -------------
44// Unlock -------------
45// ----------- SharedLock <- Invalid because the transaction can potentially observe side
46// effects of the open-threads writes!
48{
50 {
51 }
52
53 void LockShared()
54 {
55 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
56 {
57 // Transactionally pessimise LockShared -> Lock.
58 Lock();
59 }
60 else
61 {
62 State->Mutex.LockShared();
63 ensure(0 == State->TransactionalLockCount);
64 }
65 }
66
67 void UnlockShared()
68 {
69 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
70 {
71 // Transactionally pessimise UnlockShared -> Unlock.
72 Unlock();
73 }
74 else
75 {
76 ensure(0 == State->TransactionalLockCount);
77 State->Mutex.UnlockShared();
78 }
79 }
80
81 void Lock()
82 {
83 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
84 {
86 {
87 // The transactional system which can increment TransactionalLockCount
88 // is always single-threaded, thus this is safe to check without atomicity.
89 if (0 == State->TransactionalLockCount)
90 {
91 State->Mutex.Lock();
92 }
93
94 State->TransactionalLockCount += 1;
95 };
96
97 // We explicitly copy the state here for the case that `this` was stack
98 // allocated and has already died before the on-abort is hit.
100 {
101 State.Object->Unlock();
102 };
103 }
104 else
105 {
106 State->Mutex.Lock();
107 ensure(0 == State->TransactionalLockCount);
108 }
109 }
110
111 void Unlock()
112 {
113 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
114 {
115 // We explicitly copy the state here for the case that `this` was stack
116 // allocated and has already died before the on-commit is hit.
118 {
119 State.Object->Unlock();
120 };
121 }
122 else
123 {
124 ensure(0 == State->TransactionalLockCount);
125 State->Mutex.Unlock();
126 }
127 }
128
129 [[nodiscard]] bool TryLock()
130 {
131 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
132 {
133 bool Ret = false;
134
136 {
137 // The transactional system which can increment TransactionalLockCount
138 // is always single-threaded, thus this is safe to check without atomicity.
139 // For TryLock we should only lock when we have a 0 count as no one owns this lock
140 if (0 == State->TransactionalLockCount)
141 {
142 Ret = State->Mutex.TryLock();
143 }
144
145 if (Ret)
146 {
147 State->TransactionalLockCount += 1;
148 }
149 };
150
151 // Only setup the OnAbort if we *did* grab a lock; otherwise, we will not want to do
152 // anything with the count or lock.
153 if (Ret)
154 {
155 // We explicitly copy the state here for the case that `this` was stack
156 // allocated and has already died before the on-abort is hit.
158 {
159 State.Object->Unlock();
160 };
161 }
162
163 return Ret;
164 }
165
166 return State->Mutex.TryLock();
167 }
168
169private:
171
172 struct FState final
173 {
176
177 FState() = default;
178 ~FState()
179 {
181 }
182
183 void Unlock()
184 {
186
188
189 if (0 == TransactionalLockCount)
190 {
191 Mutex.Unlock();
192 }
193 }
194 };
195
197};
198
199using FTransactionallySafeSharedMutex = ::UE::FTransactionallySafeSharedMutexDefinition;
200
201#else
203#endif
204
205} // namespace UE
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define UE_NONCOPYABLE(TypeName)
Definition CoreMiscDefines.h:457
TSharedRef< InObjectType, InMode > MakeShared(InArgTypes &&... Args)
Definition SharedPointer.h:2009
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
FRWLock Lock
Definition UnversionedPropertySerialization.cpp:921
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition OpenWrapper.h:28
Definition SharedPointer.h:692
UE_API void Unlock()
Definition RecursiveMutex.cpp:115
Definition SharedMutex.h:22
UE::FRecursiveMutex Mutex
Definition MeshPaintVirtualTexture.cpp:164
State
Definition PacketHandler.h:88
Definition AdvancedWidgetsModule.cpp:13
::UE::FSharedMutex FTransactionallySafeSharedMutex
Definition TransactionallySafeSharedMutex.h:202