blob: 085fea40a4080e0aaebc14bcd27a1adff154e87c [file] [log] [blame]
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include <grpc/support/port_platform.h>
#include "src/core/lib/iomgr/lockfree_event.h"
#include <grpc/support/log.h>
#include "src/core/lib/debug/trace.h"
extern grpc_core::TraceFlag grpc_polling_trace;
/* 'state' holds the to call when the fd is readable or writable respectively.
It can contain one of the following values:
kClosureReady : The fd has an I/O event of interest but there is no
closure yet to execute
kClosureNotReady : The fd has no I/O event of interest
closure ptr : The closure to be executed when the fd has an I/O
event of interest
shutdown_error | kShutdownBit :
'shutdown_error' field ORed with kShutdownBit.
This indicates that the fd is shutdown. Since all
memory allocations are word-aligned, the lower two
bits of the shutdown_error pointer are always 0. So
it is safe to OR these with kShutdownBit
Valid state transitions:
<closure ptr> <-----3------ kClosureNotReady -----1-------> kClosureReady
| | ^ | ^ | |
| | | | | | |
| +--------------4----------+ 6 +---------2---------------+ |
| | |
| v |
+-----5-------> [shutdown_error | kShutdownBit] <-------7---------+
For 1, 4 : See SetReady() function
For 2, 3 : See NotifyOn() function
For 5,6,7: See SetShutdown() function */
namespace grpc_core {
LockfreeEvent::LockfreeEvent() { InitEvent(); }
void LockfreeEvent::InitEvent() {
/* Perform an atomic store to start the state machine.
Note carefully that LockfreeEvent *MAY* be used whilst in a destroyed
state, while a file descriptor is on a freelist. In such a state it may
be SetReady'd, and so we need to perform an atomic operation here to
ensure no races */
gpr_atm_no_barrier_store(&state_, kClosureNotReady);
}
void LockfreeEvent::DestroyEvent() {
gpr_atm curr;
do {
curr = gpr_atm_no_barrier_load(&state_);
if (curr & kShutdownBit) {
GRPC_ERROR_UNREF((grpc_error*)(curr & ~kShutdownBit));
} else {
GPR_ASSERT(curr == kClosureNotReady || curr == kClosureReady);
}
/* we CAS in a shutdown, no error value here. If this event is interacted
with post-deletion (see the note in the constructor) we want the bit
pattern to prevent error retention in a deleted object */
} while (!gpr_atm_no_barrier_cas(&state_, curr,
kShutdownBit /* shutdown, no error */));
}
void LockfreeEvent::NotifyOn(grpc_closure* closure) {
while (true) {
/* This load needs to be an acquire load because this can be a shutdown
* error that we might need to reference. Adding acquire semantics makes
* sure that the shutdown error has been initialized properly before us
* referencing it. */
gpr_atm curr = gpr_atm_acq_load(&state_);
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_ERROR, "LockfreeEvent::NotifyOn: %p curr=%p closure=%p", this,
(void*)curr, closure);
}
switch (curr) {
case kClosureNotReady: {
/* kClosureNotReady -> <closure>.
We're guaranteed by API that there's an acquire barrier before here,
so there's no need to double-dip and this can be a release-only.
The release itself pairs with the acquire half of a set_ready full
barrier. */
if (gpr_atm_rel_cas(&state_, kClosureNotReady, (gpr_atm)closure)) {
return; /* Successful. Return */
}
break; /* retry */
}
case kClosureReady: {
/* Change the state to kClosureNotReady. Schedule the closure if
successful. If not, the state most likely transitioned to shutdown.
We should retry.
This can be a no-barrier cas since the state is being transitioned to
kClosureNotReady; set_ready and set_shutdown do not schedule any
closure when transitioning out of CLOSURE_NO_READY state (i.e there
is no other code that needs to 'happen-after' this) */
if (gpr_atm_no_barrier_cas(&state_, kClosureReady, kClosureNotReady)) {
GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_NONE);
return; /* Successful. Return */
}
break; /* retry */
}
default: {
/* 'curr' is either a closure or the fd is shutdown(in which case 'curr'
contains a pointer to the shutdown-error). If the fd is shutdown,
schedule the closure with the shutdown error */
if ((curr & kShutdownBit) > 0) {
grpc_error* shutdown_err = (grpc_error*)(curr & ~kShutdownBit);
GRPC_CLOSURE_SCHED(closure,
GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"FD Shutdown", &shutdown_err, 1));
return;
}
/* There is already a closure!. This indicates a bug in the code */
gpr_log(GPR_ERROR,
"LockfreeEvent::NotifyOn: notify_on called with a previous "
"callback still pending");
abort();
}
}
}
GPR_UNREACHABLE_CODE(return );
}
bool LockfreeEvent::SetShutdown(grpc_error* shutdown_err) {
gpr_atm new_state = (gpr_atm)shutdown_err | kShutdownBit;
while (true) {
gpr_atm curr = gpr_atm_no_barrier_load(&state_);
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_ERROR, "LockfreeEvent::SetShutdown: %p curr=%p err=%s",
&state_, (void*)curr, grpc_error_string(shutdown_err));
}
switch (curr) {
case kClosureReady:
case kClosureNotReady:
/* Need a full barrier here so that the initial load in notify_on
doesn't need a barrier */
if (gpr_atm_full_cas(&state_, curr, new_state)) {
return true; /* early out */
}
break; /* retry */
default: {
/* 'curr' is either a closure or the fd is already shutdown */
/* If fd is already shutdown, we are done */
if ((curr & kShutdownBit) > 0) {
GRPC_ERROR_UNREF(shutdown_err);
return false;
}
/* Fd is not shutdown. Schedule the closure and move the state to
shutdown state.
Needs an acquire to pair with setting the closure (and get a
happens-after on that edge), and a release to pair with anything
loading the shutdown state. */
if (gpr_atm_full_cas(&state_, curr, new_state)) {
GRPC_CLOSURE_SCHED((grpc_closure*)curr,
GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
"FD Shutdown", &shutdown_err, 1));
return true;
}
/* 'curr' was a closure but now changed to a different state. We will
have to retry */
break;
}
}
}
GPR_UNREACHABLE_CODE(return false);
}
void LockfreeEvent::SetReady() {
while (true) {
gpr_atm curr = gpr_atm_no_barrier_load(&state_);
if (grpc_polling_trace.enabled()) {
gpr_log(GPR_ERROR, "LockfreeEvent::SetReady: %p curr=%p", &state_,
(void*)curr);
}
switch (curr) {
case kClosureReady: {
/* Already ready. We are done here */
return;
}
case kClosureNotReady: {
/* No barrier required as we're transitioning to a state that does not
involve a closure */
if (gpr_atm_no_barrier_cas(&state_, kClosureNotReady, kClosureReady)) {
return; /* early out */
}
break; /* retry */
}
default: {
/* 'curr' is either a closure or the fd is shutdown */
if ((curr & kShutdownBit) > 0) {
/* The fd is shutdown. Do nothing */
return;
}
/* Full cas: acquire pairs with this cas' release in the event of a
spurious set_ready; release pairs with this or the acquire in
notify_on (or set_shutdown) */
else if (gpr_atm_full_cas(&state_, curr, kClosureNotReady)) {
GRPC_CLOSURE_SCHED((grpc_closure*)curr, GRPC_ERROR_NONE);
return;
}
/* else the state changed again (only possible by either a racing
set_ready or set_shutdown functions. In both these cases, the closure
would have been scheduled for execution. So we are done here */
return;
}
}
}
}
} // namespace grpc_core