30
30
31
31
#include < tuple>
32
32
33
+ #include " dawn/common/Mutex.h"
33
34
#include " dawn/dawn_proc_table.h"
34
35
#include " dawn/wire/ChunkedCommandHandler.h"
35
36
#include " dawn/wire/Wire.h"
@@ -45,17 +46,59 @@ namespace dawn::wire::server {
45
46
ServerBase (const DawnProcTable& procs) : mProcs (procs) {}
46
47
~ServerBase () override = default ;
47
48
49
+ Mutex::AutoLock GetGuard () { return Mutex::AutoLock (&mMutex ); }
50
+
48
51
protected:
49
52
// Proc table may be used by children as well.
50
53
DawnProcTable mProcs ;
51
54
55
+ // Template functions that implement helpers on KnownObjects.
56
+ template <typename T>
57
+ WireResult Get (ObjectId id, Reserved<T>* result) {
58
+ return std::get<KnownObjects<T>>(mKnown ).Get (id, result);
59
+ }
60
+ template <typename T>
61
+ WireResult Get (ObjectId id, Known<T>* result) {
62
+ return std::get<KnownObjects<T>>(mKnown ).Get (id, result);
63
+ }
64
+ template <typename T>
65
+ WireResult FillReservation (ObjectId id, T handle, Known<T>* known = nullptr ) {
66
+ auto result = std::get<KnownObjects<T>>(mKnown ).FillReservation (id, handle, known);
67
+ if (result == WireResult::FatalError) {
68
+ Release (handle);
69
+ }
70
+ return result;
71
+ }
52
72
template <typename T>
53
- const KnownObjects<T>& Objects () const {
54
- return std::get<KnownObjects<T>>(mKnown );
73
+ WireResult Allocate (Reserved<T>* result,
74
+ ObjectHandle handler,
75
+ AllocationState state = AllocationState::Allocated) {
76
+ // Allocations always take the lock because |vector::push_back| may be called which
77
+ // can invalidate pointers.
78
+ auto serverGuard = GetGuard ();
79
+ return std::get<KnownObjects<T>>(mKnown ).Allocate (result, handler, state);
55
80
}
56
81
template <typename T>
57
- KnownObjects<T>& Objects () {
58
- return std::get<KnownObjects<T>>(mKnown );
82
+ WireResult Free (ObjectId id, ObjectData<T>* data) {
83
+ // Free always take the lock to ensure that any callback handlers (which always take
84
+ // the lock), i.e. On*Callback functions, cannot race with object deletion.
85
+ auto serverGuard = GetGuard ();
86
+ Reserved<T> obj;
87
+ WIRE_TRY (Get (id, &obj));
88
+ *data = std::move (*obj.data );
89
+ std::get<KnownObjects<T>>(mKnown ).Free (id);
90
+ return WireResult::Success;
91
+ }
92
+
93
+ // Special functions that are currently needed.
94
+ bool IsDeviceKnown (WGPUDevice device) const {
95
+ return std::get<KnownObjects<WGPUDevice>>(mKnown ).IsKnown (device);
96
+ }
97
+ std::vector<WGPUDevice> GetAllDeviceHandles () const {
98
+ return std::get<KnownObjects<WGPUDevice>>(mKnown ).GetAllHandles ();
99
+ }
100
+ std::vector<WGPUInstance> GetAllInstanceHandles () const {
101
+ return std::get<KnownObjects<WGPUInstance>>(mKnown ).GetAllHandles ();
59
102
}
60
103
61
104
template <typename T>
@@ -65,7 +108,8 @@ namespace dawn::wire::server {
65
108
void DestroyAllObjects () {
66
109
// * Release devices first to force completion of any async work.
67
110
{
68
- std::vector<WGPUDevice> handles = Objects<WGPUDevice>().AcquireAllHandles ();
111
+ std::vector<WGPUDevice> handles =
112
+ std::get<KnownObjects<WGPUDevice>>(mKnown ).AcquireAllHandles ();
69
113
for (WGPUDevice handle : handles) {
70
114
Release (handle);
71
115
}
@@ -74,7 +118,8 @@ namespace dawn::wire::server {
74
118
{% for type in by_category[" object" ] if type.name .get () != " device" %}
75
119
{% set cType = as_cType (type.name ) %}
76
120
{
77
- std::vector<{{cType}}> handles = Objects<{{cType}}>().AcquireAllHandles ();
121
+ std::vector<{{cType}}> handles =
122
+ std::get<KnownObjects<{{cType}}>>(mKnown ).AcquireAllHandles ();
78
123
for ({{cType}} handle : handles) {
79
124
Release (handle);
80
125
}
@@ -87,7 +132,7 @@ namespace dawn::wire::server {
87
132
{% for type in by_category[" object" ] %}
88
133
{% set cType = as_cType (type.name ) %}
89
134
WireResult GetFromId (ObjectId id, {{cType}}* out) const final {
90
- return Objects< {{cType}}>( ).GetNativeHandle (id, out);
135
+ return std::get<KnownObjects< {{cType}}>>( mKnown ).GetNativeHandle (id, out);
91
136
}
92
137
93
138
WireResult GetOptionalFromId (ObjectId id, {{cType}}* out) const final {
@@ -99,7 +144,34 @@ namespace dawn::wire::server {
99
144
}
100
145
{% endfor %}
101
146
102
- // * The list of known IDs for each object type.
147
+ // The list of known IDs for each object type.
148
+ // We use an explicit Mutex to protect these lists instead of MutexProtected because:
149
+ // 1) It allows us to return AutoLock objects to hold the lock across function scopes.
150
+ // 2) It allows us to use |mKnown| when we know we can access it without the lock.
151
+ // To clarify the threading model of the KnownObjects, there is always a "main" thread on
152
+ // the server which is responsible for flushing commands from the client. This thread is
153
+ // the only thread that ever calls |HandleCommandsImpl| which ultimately calls into the
154
+ // command handlers in the generated ServerHandlers.cpp. All other threads that can
155
+ // interact with |mKnown| are async or spontaneous callback threads which always hold the
156
+ // lock for the full duration of their call, see |ForwardToServerHelper| where we force all
157
+ // callbacks to take the lock. On the "main" thread, we only acquire the lock whenever we
158
+ // |Allocate| or |Free|. We need to lock w.r.t other threads for |Allocate| because
159
+ // internally, that can cause a |std::vector::push_back| call which may invalidate all
160
+ // pointers. As a result, we cannot have another thread doing anything during that window.
161
+ // Similarly, for |Free|, we may release the WGPU* backing handle which is also not allowed
162
+ // to race. Reads on the "main" thread are not explicitly synchronized because callbacks
163
+ // are not allowed to allocate or free objects which guarantees that the reads on the
164
+ // "main" thread will be pointer stable. Furthermore, it is actually necessary that we
165
+ // don't hold the lock for reads on the "main" thread because APIs like |Device::Destroy|
166
+ // can cause us to wait on callbacks which as stated above, require the lock to complete.
167
+ // So if we were to hold the lock on the "main" thread during the read and execution of
168
+ // |Device::Destroy|, we could deadlock when we try waiting for callbacks which can't
169
+ // acquire this lock.
170
+ // TODO(https://crbug.com/412761856): Revisit this logic and consider using a rw-lock
171
+ // in conjunction with taking additional refs to "special" objects/function pairs that can
172
+ // cause callbacks to run when called. This would make this logic less specific to the way
173
+ // that the Dawn tests and Chromium uses the wire.
174
+ Mutex mMutex ;
103
175
std::tuple<
104
176
{% for type in by_category[" object" ] %}
105
177
KnownObjects<{{as_cType (type.name )}}>{{ " , " if not loop.last else " " }}
0 commit comments