UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MTTransactionallySafeAccessDetector.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "AutoRTFM.h"
7#include "HAL/Platform.h"
8#include "MTAccessDetector.h"
11
12#if ENABLE_MT_DETECTOR
13
14// A transactionally safe access detector that works in the following novel ways:
15// - In the open (non-transactional):
16// - Acquires the read/write access like before.
17// - Release the read/write access like before.
18// - In the closed (transactional):
19// - During acquiring read access we query `ReadLockCount`:
20// - 0 means we haven't taken read access before in our transaction nest and we acquire it.
21// - But **only** if we haven't previously taken write access (by querying `WriteLockCount`).
22// - Then we bump `ReadLockCount` to remember we did a read.
23// - We also register an on-abort handler to release the access.
24// - During acquiring write access we query `WriteLockCount`:
25// - 0 means we haven't taken write access before in our transaction nest and we acquire it.
26// - But if `ReadLockCount` was non-zero then we have to upgrade the access from read to write.
27// - Then we bump `WriteLockCount` to remember we did a write.
28// - During releases we defer these to on-commit.
29//
30// During on-commit we always release all our `ReadLockCount`'s first, so that we handle the case
31// correctly where we had read then write, we need to only actually release the write access and
32// this means we correctly handle that case.
34{
36 {
37 }
38
39 // All the definitions in here are private because we do not want arbitrary code
40 // to call the acquire/release read/write access functions directly, and instead
41 // they should use `UE_MT_SCOPED_READ_ACCESS` and `UE_MT_SCOPED_WRITE_ACCESS`.
42 // This restriction means that all acquires and releases are pairs that must
43 // happen together in the open, or together in closed.
44private:
49 inline bool AcquireReadAccess() const
50 {
51 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
52 {
53 const bool bResult = AutoRTFM::Open([&]
54 {
55 // The transactional system which can increment the lock counts
56 // is always single-threaded, thus this is safe to check without atomicity.
57 if (0 == State->ReadLockCount && 0 == State->WriteLockCount)
58 {
59 if (UNLIKELY(!State->Detector.AcquireReadAccess()))
60 {
61 return false;
62 }
63 }
64
65 State->ReadLockCount += 1;
66 return true;
67 });
68
69 if (UNLIKELY(!bResult))
70 {
71 return false;
72 }
73
75 return true;
76 }
77 else
78 {
79 if (UNLIKELY(!State->Detector.AcquireReadAccess()))
80 {
81 return false;
82 }
83
84 ensure(0 == State->WriteLockCount);
85 return true;
86 }
87 }
88
93 inline bool ReleaseReadAccess() const
94 {
95 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
96 {
98
99 // We can't do anything better here than returning true, because we
100 // are deferring the actual release until on commit!
101 return true;
102 }
103 else
104 {
105 ensure(0 == State->WriteLockCount);
106 return State->Detector.ReleaseReadAccess();
107 }
108 }
109
114 inline bool AcquireWriteAccess() const
115 {
116 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
117 {
118 const bool bResult = AutoRTFM::Open([&]
119 {
120 if ((0 == State->ReadLockCount) && (0 == State->WriteLockCount))
121 {
122 // There have been no prior calls to `AcquireReadAccess`
123 // so we can just claim write access directly.
124 if (UNLIKELY(!State->Detector.AcquireWriteAccess()))
125 {
126 return false;
127 }
128 }
129 else if (0 == State->WriteLockCount)
130 {
131 // There was a prior call to `AcquireReadAccess` so we need
132 // to upgrade our read access to write access.
133 if (UNLIKELY(!State->Detector.UpgradeReadAccessToWriteAccess()))
134 {
135 return false;
136 }
137 }
138
139 State->WriteLockCount += 1;
140 return true;
141 });
142
143 if (UNLIKELY(!bResult))
144 {
145 return false;
146 }
147
149 return true;
150 }
151 else
152 {
153 if (UNLIKELY(!State->Detector.AcquireWriteAccess()))
154 {
155 return false;
156 }
157
158 ensure((0 == State->ReadLockCount) && (0 == State->WriteLockCount));
159 return true;
160 }
161 }
162
167 inline bool ReleaseWriteAccess() const
168 {
169 if (AutoRTFM::IsTransactional() || AutoRTFM::IsCommittingOrAborting())
170 {
172
173 // We can't do anything better here than returning true, because we
174 // are deferring the actual release until on commit!
175 return true;
176 }
177 else
178 {
179 ensure((0 == State->ReadLockCount) && (0 == State->WriteLockCount));
180 return State->Detector.ReleaseWriteAccess();
181 }
182 }
183
184private:
185 struct FState final
186 {
187 // The underlying FRWAccessDetector.
189
190 // True if the abort handler has been registered.
191 bool AbortHandlerRegistered = false;
192
193 unsigned ReadLockCount = 0;
194
195 unsigned WriteLockCount = 0;
196
197 // Constructor is always open because FRWAccessDetector is not transactionally safe.
199 FState() = default;
200
201 // Destructor is always open because FRWAccessDetector is not transactionally safe.
203 ~FState()
204 {
205 ensure(0 == ReadLockCount);
207 }
208 };
209
211 {
212 if (0 < State->ReadLockCount)
213 {
214 State->ReadLockCount -= 1;
215
216 if ((0 == State->ReadLockCount) && (0 == State->WriteLockCount))
217 {
218 State->Detector.ReleaseReadAccess();
219 }
220 }
221 else if (0 < State->WriteLockCount)
222 {
223 State->WriteLockCount -= 1;
224
225 if (0 == State->WriteLockCount)
226 {
227 State->Detector.ReleaseWriteAccess();
228 }
229 }
230 else
231 {
232 // We should only register as many on-abort handlers as we had lock count increments!
233 check(false);
234 }
235 }
236
238 {
239 // We explicitly copy the state here for the case that `this` was stack
240 // allocated and has already died before the on-abort is hit.
241 AutoRTFM::OnAbort([State = AutoRTFM::TOpenWrapper{this->State}]
242 {
243 ReleaseAccess(State.Object);
244 });
245 }
246
248 {
249 // We explicitly copy the state here for the case that `this` was stack
250 // allocated and has already died before the on-abort is hit.
251 AutoRTFM::OnCommit([State = AutoRTFM::TOpenWrapper{this->State}]
252 {
253 ReleaseAccess(State.Object);
254 });
255 }
256
257 // The state held for calls made when in a transaction.
259
264};
265
266// TODO: if we made `FRWAccessDetector` have private + friend like below we don't need this anymore!
268{
273};
274
275#if UE_AUTORTFM
277#else
279#endif
280
281#define UE_MT_DECLARE_TS_RW_ACCESS_DETECTOR(AccessDetector) FRWTransactionallySafeAccessDetector AccessDetector;
282
283#else // ENABLE_MT_DETECTOR
284
285#define UE_MT_DECLARE_TS_RW_ACCESS_DETECTOR(AccessDetector)
286
287#endif // ENABLE_MT_DETECTOR
#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_FORCEINLINE_HINT
Definition Platform.h:723
#define UNLIKELY(x)
Definition Platform.h:857
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
Definition OpenWrapper.h:28
Definition SharedPointer.h:692
State
Definition PacketHandler.h:88