UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
TransactionallySafeMutex.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Async/Mutex.h"
6#include "AutoRTFM.h"
9
10namespace UE
11{
12
13#if UE_AUTORTFM
14
15// A transactionally safe mutex that works in the following novel ways:
16// - In the open (non-transactional):
17// - Take the lock like before. Simple!
18// - Free the lock like before too.
19// - In the closed (transactional):
20// - Unlock() decrements `TransactionalLockCount` and registers the on-commit handler (if not
21// already registered) to perform the deferred unlock to the underlying mutex, if not rebalanced
22// in the transaction with a lock.
23// - During locking we query `TransactionalLockCount`:
24// * -1 means the transaction performed an Unlock() before the lock, so the underlying mutex
25// must have been locked prior to the start of the transaction. `TransactionalLockCount` is
26// incremented to 0, and a lock is emulated (i.e. TryLock() will return `true` without
27// touching the underlying mutex).
28// * 0 means the underlying mutex will be locked if it hasn't been already (tracked by
29// `bTransactionallyLocked`). Once the underlying mutex is locked, it remains locked for the
30// entire duration of the transaction. This is done as a transactional abort will likely undo
31// the writes to memory that is guarded by the mutex, so the lock needs be held until the
32// transaction is either fully committed or aborted. When the underlying mutex is locked an
33// on-abort handler and on-commit handler are registered to unlock the underlying mutex if
34// necessary.
35//
36// Thus with this approach we will hold this lock for the *entirety* of the transactional nest should
37// we take the lock during the transaction, thus preventing non-transactional code from seeing any
38// modifications we should make.
40{
41 // Always open because the constructor will create the underlying mutex.
44 {
45 }
46
47 // Construct in a locked state.
50 {
51 }
52
53 void Lock()
54 {
55 if (AutoRTFM::IsClosed() || AutoRTFM::IsCommittingOrAborting())
56 {
58 }
59 else if (AutoRTFM::IsTransactional())
60 {
62 }
63 else
64 {
65 // Non-transactional path. Call Lock() on the mutex directly.
66 State->MutexLock();
67 }
68 }
69
70 [[nodiscard]] inline bool TryLock()
71 {
72 if (AutoRTFM::IsClosed() || AutoRTFM::IsCommittingOrAborting())
73 {
75 }
76 else if (AutoRTFM::IsTransactional())
77 {
79 }
80 else
81 {
82 // Non-transactional path. Call TryLock() on the mutex directly.
83 return State->Mutex.TryLock();
84 }
85 }
86
87 void Unlock()
88 {
89 if (AutoRTFM::IsClosed() || AutoRTFM::IsCommittingOrAborting())
90 {
92 }
93 else if (AutoRTFM::IsTransactional())
94 {
96 }
97 else
98 {
99 State->MutexUnlock();
100 }
101 }
102
113 [[nodiscard]] bool IsLocked() const
114 {
115 return State->MutexIsLocked();
116 }
117
118private:
120
121 template <bool (FTransactionallySafeMutex::*Functor)()>
122 bool InTheClosed()
123 {
124 bool Result;
125 AutoRTFM::EContextStatus Status = AutoRTFM::Close([this, &Result]
126 {
127 Result = (this->*Functor)();
128 });
129 check(Status == AutoRTFM::EContextStatus::OnTrack);
130 return Result;
131 }
132
134 {
135 check(AutoRTFM::IsClosed() || AutoRTFM::IsCommittingOrAborting());
136 ensure(State->TransactionalLockCount >= -1 && State->TransactionalLockCount <= 1);
137
138 if (State->TransactionalLockCount > 0)
139 {
140 return false; // Attempting double-lock within transaction.
141 }
142
143 if (State->bTransactionallyLocked)
144 {
145 // Underlying mutex has already been locked for the duration of the transaction.
146 State->TransactionalLockCount++;
147 return true;
148 }
149
150 if (State->TransactionalLockCount < 0)
151 {
152 // The mutex was locked before the transaction and unlocked inside the transaction.
153 // As unlock is deferred, bump the lock counter and return true to emulate the lock.
154 State->TransactionalLockCount++;
155 return true;
156 }
157
158 // Attempt to lock the underlying mutex.
159 if (!State->MutexTryLock())
160 {
161 return false;
162 }
163
164 // First time the mutex has been locked during the transaction.
165 // Increment the lock count, mark the transactionally locked flag and register
166 // on-commit and on-abort handlers.
167 State->TransactionalLockCount++;
169 return true;
170 };
171
172 // Acquires the mutex in a transactionally-safe way. Assumes we are either in the closed or
173 // are mid-commit/mid-abort.
175 {
176 check(AutoRTFM::IsClosed() || AutoRTFM::IsCommittingOrAborting());
177 ensure(State->TransactionalLockCount >= -1 && State->TransactionalLockCount <= 0);
178
179 State->TransactionalLockCount++;
180
181 if (State->bTransactionallyLocked)
182 {
183 // Underlying mutex has already been locked for the duration of the transaction.
184 return true;
185 }
186
187 if (State->TransactionalLockCount == 0)
188 {
189 // The mutex was locked before the transaction and unlocked inside the transaction.
190 return true;
191 }
192
193 // First time the mutex has been locked during the transaction.
194 // Mark the transactionally locked flag and register on-commit and on-abort handlers.
195 State->MutexLock();
197 return true;
198 }
199
200 // Releases the mutex in a transactionally-safe way. Assumes we are either in the closed or
201 // are mid-commit/mid-abort.
203 {
204 check(AutoRTFM::IsClosed() || AutoRTFM::IsCommittingOrAborting());
205 ensure(State->TransactionalLockCount >= 0 && State->TransactionalLockCount <= 1);
206
207 State->TransactionalLockCount--;
208 // Use an on-commit handler to unlock the underlying mutex if not balanced with a lock
209 // before the transaction commits.
210 MaybeRegisterCommitHandler();
211 return true;
212 }
213
214 // Called when the underlying mutex is locked for the first time within a transaction nest.
215 // Sets the bTransactionallyLocked flag, calls MaybeRegisterCommitHandler() and registers an
216 // on-abort handler to unlock the underlying mutex.
218 {
219 ensure(!State->bTransactionallyLocked);
220 State->bTransactionallyLocked = true;
221
222 MaybeRegisterCommitHandler();
223
224 // Capture State instead of 'this' as State is a TSharedPtr which can outlive this
225 // FTransactionallySafeMutex.
226 AutoRTFM::OnAbort([State = AutoRTFM::TOpenWrapper{this->State}]
227 {
228 State.Object->MutexUnlock();
229 State.Object->ResetTransactionState();
230 });
231 }
232
233 // Registers an on-commit handler (if it hasn't been already registered for this transaction
234 // nest) to unlock the underlying mutex if the transaction unlocked more times than locked.
235 void MaybeRegisterCommitHandler()
236 {
237 if (!State->bRegisteredCommitHandler)
238 {
239 State->bRegisteredCommitHandler = true;
240
241 // Capture State instead of 'this' as State is a TSharedPtr which can outlive this
242 // FTransactionallySafeMutex.
243 AutoRTFM::OnCommit([State = AutoRTFM::TOpenWrapper{this->State}]
244 {
245 // If the transaction's lock count is negative (more unlocks than locks), or the
246 // underlying mutex was locked and then re-balanced to an unlocked state, unlock
247 // the underlying mutex.
248 if (State.Object->TransactionalLockCount < 0 ||
249 (State.Object->bTransactionallyLocked && State.Object->TransactionalLockCount == 0))
250 {
251 State.Object->MutexUnlock();
252 }
253 State.Object->ResetTransactionState();
254 });
255 }
256 }
257
258 struct FState final
259 {
261 FState() : bTransactionallyLocked(false), bRegisteredCommitHandler(false) {}
262
264 explicit FState(UE::FAcquireLock Tag) : Mutex(Tag), bTransactionallyLocked(false), bRegisteredCommitHandler(false) {}
265
267 ~FState()
268 {
271 ensure(bRegisteredCommitHandler == false);
272 }
273
276 {
279 bRegisteredCommitHandler = false;
280 }
281
283 void MutexLock()
284 {
285 Mutex.Lock();
286 }
287
289 bool MutexTryLock()
290 {
291 return Mutex.TryLock();
292 }
293
295 void MutexUnlock()
296 {
297 Mutex.Unlock();
298 }
299
301 bool MutexIsLocked()
302 {
303 return Mutex.IsLocked();
304 }
305
306 // The underlying mutex.
308 // Counter for calls to unlock / lock within a transaction:
309 // -1: mutex has had one more unlock than lock. (e.g. transaction starts locked)
310 // 0: mutex has had the same number of locks as unlocks.
311 // +1: mutex has had one more lock than unlock.
312 // Other values indicate a double-lock or double-unlock within a transaction.
314 // True if the underlying mutex has been locked for the duration of the transaction.
315 bool bTransactionallyLocked : 1;
316 // True if the on-commit handler has been registered for this transaction nest.
317 bool bRegisteredCommitHandler : 1;
318 };
319
321};
322
323#else
325#endif // UE_AUTORTFM
326
327} // UE
328
329// Some existing code references FTransactionallySafeMutex without the UE
330// namespace prefix. TODO: properly qualify these and remove this.
#define check(expr)
Definition AssertionMacros.h:314
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define UE_AUTORTFM_NOAUTORTFM
Definition AutoRTFMDefines.h:113
#define UE_AUTORTFM_ALWAYS_OPEN
Definition AutoRTFMDefines.h:114
#define UE_NONCOPYABLE(TypeName)
Definition CoreMiscDefines.h:457
FPlatformTypes::int8 int8
An 8-bit signed integer.
Definition Platform.h:1121
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
Definition OpenWrapper.h:28
Definition SharedPointer.h:692
Definition Mutex.h:18
bool IsLocked() const
Definition RecursiveMutex.h:26
UE_API void Lock()
Definition RecursiveMutex.cpp:40
UE_API bool TryLock()
Definition RecursiveMutex.cpp:13
UE_API void Unlock()
Definition RecursiveMutex.cpp:115
UE::FRecursiveMutex Mutex
Definition MeshPaintVirtualTexture.cpp:164
State
Definition PacketHandler.h:88
UE_STRING_CLASS Result(Forward< LhsType >(Lhs), RhsLen)
Definition String.cpp.inl:732
Definition AdvancedWidgetsModule.cpp:13
FMutex FTransactionallySafeMutex
Definition TransactionallySafeMutex.h:324
@ false
Definition radaudio_common.h:23
Definition LockTags.h:9