1 /*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include <grpc/support/port_platform.h>
20
21 #include "src/core/lib/iomgr/lockfree_event.h"
22
23 #include <grpc/support/log.h>
24
25 #include "src/core/lib/debug/trace.h"
26 #include "src/core/lib/iomgr/exec_ctx.h"
27
28 extern grpc_core::DebugOnlyTraceFlag grpc_polling_trace;
29
30 /* 'state' holds the to call when the fd is readable or writable respectively.
31 It can contain one of the following values:
32 kClosureReady : The fd has an I/O event of interest but there is no
33 closure yet to execute
34
35 kClosureNotReady : The fd has no I/O event of interest
36
37 closure ptr : The closure to be executed when the fd has an I/O
38 event of interest
39
40 shutdown_error | kShutdownBit :
41 'shutdown_error' field ORed with kShutdownBit.
42 This indicates that the fd is shutdown. Since all
43 memory allocations are word-aligned, the lower two
44 bits of the shutdown_error pointer are always 0. So
45 it is safe to OR these with kShutdownBit
46
47 Valid state transitions:
48
49 <closure ptr> <-----3------ kClosureNotReady -----1-------> kClosureReady
50 | | ^ | ^ | |
51 | | | | | | |
52 | +--------------4----------+ 6 +---------2---------------+ |
53 | | |
54 | v |
55 +-----5-------> [shutdown_error | kShutdownBit] <-------7---------+
56
57 For 1, 4 : See SetReady() function
58 For 2, 3 : See NotifyOn() function
59 For 5,6,7: See SetShutdown() function */
60
61 namespace grpc_core {
62
LockfreeEvent()63 LockfreeEvent::LockfreeEvent() { InitEvent(); }
64
InitEvent()65 void LockfreeEvent::InitEvent() {
66 /* Perform an atomic store to start the state machine.
67
68 Note carefully that LockfreeEvent *MAY* be used whilst in a destroyed
69 state, while a file descriptor is on a freelist. In such a state it may
70 be SetReady'd, and so we need to perform an atomic operation here to
71 ensure no races */
72 gpr_atm_no_barrier_store(&state_, kClosureNotReady);
73 }
74
DestroyEvent()75 void LockfreeEvent::DestroyEvent() {
76 gpr_atm curr;
77 do {
78 curr = gpr_atm_no_barrier_load(&state_);
79 if (curr & kShutdownBit) {
80 #ifdef GRPC_ERROR_IS_ABSEIL_STATUS
81 internal::StatusFreeHeapPtr(curr & ~kShutdownBit);
82 #else
83 GRPC_ERROR_UNREF((grpc_error_handle)(curr & ~kShutdownBit));
84 #endif
85 } else {
86 GPR_ASSERT(curr == kClosureNotReady || curr == kClosureReady);
87 }
88 /* we CAS in a shutdown, no error value here. If this event is interacted
89 with post-deletion (see the note in the constructor) we want the bit
90 pattern to prevent error retention in a deleted object */
91 } while (!gpr_atm_no_barrier_cas(&state_, curr,
92 kShutdownBit /* shutdown, no error */));
93 }
94
NotifyOn(grpc_closure * closure)95 void LockfreeEvent::NotifyOn(grpc_closure* closure) {
96 while (true) {
97 /* This load needs to be an acquire load because this can be a shutdown
98 * error that we might need to reference. Adding acquire semantics makes
99 * sure that the shutdown error has been initialized properly before us
100 * referencing it. */
101 gpr_atm curr = gpr_atm_acq_load(&state_);
102 if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
103 gpr_log(GPR_DEBUG,
104 "LockfreeEvent::NotifyOn: %p curr=%" PRIxPTR " closure=%p", this,
105 curr, closure);
106 }
107 switch (curr) {
108 case kClosureNotReady: {
109 /* kClosureNotReady -> <closure>.
110
111 We're guaranteed by API that there's an acquire barrier before here,
112 so there's no need to double-dip and this can be a release-only.
113
114 The release itself pairs with the acquire half of a set_ready full
115 barrier. */
116 if (gpr_atm_rel_cas(&state_, kClosureNotReady,
117 reinterpret_cast<gpr_atm>(closure))) {
118 return; /* Successful. Return */
119 }
120
121 break; /* retry */
122 }
123
124 case kClosureReady: {
125 /* Change the state to kClosureNotReady. Schedule the closure if
126 successful. If not, the state most likely transitioned to shutdown.
127 We should retry.
128
129 This can be a no-barrier cas since the state is being transitioned to
130 kClosureNotReady; set_ready and set_shutdown do not schedule any
131 closure when transitioning out of CLOSURE_NO_READY state (i.e there
132 is no other code that needs to 'happen-after' this) */
133 if (gpr_atm_no_barrier_cas(&state_, kClosureReady, kClosureNotReady)) {
134 ExecCtx::Run(DEBUG_LOCATION, closure, GRPC_ERROR_NONE);
135 return; /* Successful. Return */
136 }
137
138 break; /* retry */
139 }
140
141 default: {
142 /* 'curr' is either a closure or the fd is shutdown(in which case 'curr'
143 contains a pointer to the shutdown-error). If the fd is shutdown,
144 schedule the closure with the shutdown error */
145 if ((curr & kShutdownBit) > 0) {
146 #ifdef GRPC_ERROR_IS_ABSEIL_STATUS
147 grpc_error_handle shutdown_err =
148 internal::StatusGetFromHeapPtr(curr & ~kShutdownBit);
149 #else
150 grpc_error_handle shutdown_err =
151 reinterpret_cast<grpc_error_handle>(curr & ~kShutdownBit);
152 #endif
153 ExecCtx::Run(DEBUG_LOCATION, closure,
154 GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
155 "FD Shutdown", &shutdown_err, 1));
156 return;
157 }
158
159 /* There is already a closure!. This indicates a bug in the code */
160 gpr_log(GPR_ERROR,
161 "LockfreeEvent::NotifyOn: notify_on called with a previous "
162 "callback still pending");
163 abort();
164 }
165 }
166 }
167
168 GPR_UNREACHABLE_CODE(return );
169 }
170
SetShutdown(grpc_error_handle shutdown_error)171 bool LockfreeEvent::SetShutdown(grpc_error_handle shutdown_error) {
172 #ifdef GRPC_ERROR_IS_ABSEIL_STATUS
173 intptr_t status_ptr = internal::StatusAllocHeapPtr(shutdown_error);
174 gpr_atm new_state = status_ptr | kShutdownBit;
175 #else
176 gpr_atm new_state = reinterpret_cast<gpr_atm>(shutdown_error) | kShutdownBit;
177 #endif
178
179 while (true) {
180 gpr_atm curr = gpr_atm_no_barrier_load(&state_);
181 if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
182 gpr_log(GPR_DEBUG,
183 "LockfreeEvent::SetShutdown: %p curr=%" PRIxPTR " err=%s",
184 &state_, curr, grpc_error_std_string(shutdown_error).c_str());
185 }
186 switch (curr) {
187 case kClosureReady:
188 case kClosureNotReady:
189 /* Need a full barrier here so that the initial load in notify_on
190 doesn't need a barrier */
191 if (gpr_atm_full_cas(&state_, curr, new_state)) {
192 return true; /* early out */
193 }
194 break; /* retry */
195
196 default: {
197 /* 'curr' is either a closure or the fd is already shutdown */
198
199 /* If fd is already shutdown, we are done */
200 if ((curr & kShutdownBit) > 0) {
201 #ifdef GRPC_ERROR_IS_ABSEIL_STATUS
202 internal::StatusFreeHeapPtr(status_ptr);
203 #else
204 GRPC_ERROR_UNREF(shutdown_error);
205 #endif
206 return false;
207 }
208
209 /* Fd is not shutdown. Schedule the closure and move the state to
210 shutdown state.
211 Needs an acquire to pair with setting the closure (and get a
212 happens-after on that edge), and a release to pair with anything
213 loading the shutdown state. */
214 if (gpr_atm_full_cas(&state_, curr, new_state)) {
215 ExecCtx::Run(DEBUG_LOCATION, reinterpret_cast<grpc_closure*>(curr),
216 GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
217 "FD Shutdown", &shutdown_error, 1));
218 return true;
219 }
220
221 /* 'curr' was a closure but now changed to a different state. We will
222 have to retry */
223 break;
224 }
225 }
226 }
227
228 GPR_UNREACHABLE_CODE(return false);
229 }
230
SetReady()231 void LockfreeEvent::SetReady() {
232 while (true) {
233 gpr_atm curr = gpr_atm_no_barrier_load(&state_);
234
235 if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) {
236 gpr_log(GPR_DEBUG, "LockfreeEvent::SetReady: %p curr=%" PRIxPTR, &state_,
237 curr);
238 }
239
240 switch (curr) {
241 case kClosureReady: {
242 /* Already ready. We are done here */
243 return;
244 }
245
246 case kClosureNotReady: {
247 /* No barrier required as we're transitioning to a state that does not
248 involve a closure */
249 if (gpr_atm_no_barrier_cas(&state_, kClosureNotReady, kClosureReady)) {
250 return; /* early out */
251 }
252 break; /* retry */
253 }
254
255 default: {
256 /* 'curr' is either a closure or the fd is shutdown */
257 if ((curr & kShutdownBit) > 0) {
258 /* The fd is shutdown. Do nothing */
259 return;
260 }
261 /* Full cas: acquire pairs with this cas' release in the event of a
262 spurious set_ready; release pairs with this or the acquire in
263 notify_on (or set_shutdown) */
264 else if (gpr_atm_full_cas(&state_, curr, kClosureNotReady)) {
265 ExecCtx::Run(DEBUG_LOCATION, reinterpret_cast<grpc_closure*>(curr),
266 GRPC_ERROR_NONE);
267 return;
268 }
269 /* else the state changed again (only possible by either a racing
270 set_ready or set_shutdown functions. In both these cases, the closure
271 would have been scheduled for execution. So we are done here */
272 return;
273 }
274 }
275 }
276 }
277
278 } // namespace grpc_core
279