/* * * 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 #include "src/core/lib/iomgr/call_combiner.h" #include #include #include "src/core/lib/debug/stats.h" #include "src/core/lib/profiling/timers.h" grpc_core::TraceFlag grpc_call_combiner_trace(false, "call_combiner"); static grpc_error* decode_cancel_state_error(gpr_atm cancel_state) { if (cancel_state & 1) { return (grpc_error*)(cancel_state & ~static_cast(1)); } return GRPC_ERROR_NONE; } static gpr_atm encode_cancel_state_error(grpc_error* error) { return static_cast(1) | (gpr_atm)error; } #ifdef GRPC_TSAN_ENABLED static void tsan_closure(void* user_data, grpc_error* error) { grpc_call_combiner* call_combiner = static_cast(user_data); // We ref-count the lock, and check if it's already taken. // If it was taken, we should do nothing. Otherwise, we will mark it as // locked. Note that if two different threads try to do this, only one of // them will be able to mark the lock as acquired, while they both run their // callbacks. In such cases (which should never happen for call_combiner), // TSAN will correctly produce an error. // // TODO(soheil): This only covers the callbacks scheduled by // grpc_call_combiner_(start|finish). If in the future, a // callback gets scheduled using other mechanisms, we will need // to add APIs to externally lock call combiners. grpc_core::RefCountedPtr lock = call_combiner->tsan_lock; bool prev = false; if (lock->taken.compare_exchange_strong(prev, true)) { TSAN_ANNOTATE_RWLOCK_ACQUIRED(&lock->taken, true); } else { lock.reset(); } GRPC_CLOSURE_RUN(call_combiner->original_closure, GRPC_ERROR_REF(error)); if (lock != nullptr) { TSAN_ANNOTATE_RWLOCK_RELEASED(&lock->taken, true); bool prev = true; GPR_ASSERT(lock->taken.compare_exchange_strong(prev, false)); } } #endif static void call_combiner_sched_closure(grpc_call_combiner* call_combiner, grpc_closure* closure, grpc_error* error) { #ifdef GRPC_TSAN_ENABLED call_combiner->original_closure = closure; GRPC_CLOSURE_SCHED(&call_combiner->tsan_closure, error); #else GRPC_CLOSURE_SCHED(closure, error); #endif } void grpc_call_combiner_init(grpc_call_combiner* call_combiner) { gpr_atm_no_barrier_store(&call_combiner->cancel_state, 0); gpr_atm_no_barrier_store(&call_combiner->size, 0); gpr_mpscq_init(&call_combiner->queue); #ifdef GRPC_TSAN_ENABLED GRPC_CLOSURE_INIT(&call_combiner->tsan_closure, tsan_closure, call_combiner, grpc_schedule_on_exec_ctx); #endif } void grpc_call_combiner_destroy(grpc_call_combiner* call_combiner) { gpr_mpscq_destroy(&call_combiner->queue); GRPC_ERROR_UNREF(decode_cancel_state_error(call_combiner->cancel_state)); } #ifndef NDEBUG #define DEBUG_ARGS , const char *file, int line #define DEBUG_FMT_STR "%s:%d: " #define DEBUG_FMT_ARGS , file, line #else #define DEBUG_ARGS #define DEBUG_FMT_STR #define DEBUG_FMT_ARGS #endif void grpc_call_combiner_start(grpc_call_combiner* call_combiner, grpc_closure* closure, grpc_error* error DEBUG_ARGS, const char* reason) { GPR_TIMER_SCOPE("call_combiner_start", 0); if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, "==> grpc_call_combiner_start() [%p] closure=%p [" DEBUG_FMT_STR "%s] error=%s", call_combiner, closure DEBUG_FMT_ARGS, reason, grpc_error_string(error)); } size_t prev_size = static_cast( gpr_atm_full_fetch_add(&call_combiner->size, (gpr_atm)1)); if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " size: %" PRIdPTR " -> %" PRIdPTR, prev_size, prev_size + 1); } GRPC_STATS_INC_CALL_COMBINER_LOCKS_SCHEDULED_ITEMS(); if (prev_size == 0) { GRPC_STATS_INC_CALL_COMBINER_LOCKS_INITIATED(); GPR_TIMER_MARK("call_combiner_initiate", 0); if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " EXECUTING IMMEDIATELY"); } // Queue was empty, so execute this closure immediately. call_combiner_sched_closure(call_combiner, closure, error); } else { if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " QUEUING"); } // Queue was not empty, so add closure to queue. closure->error_data.error = error; gpr_mpscq_push(&call_combiner->queue, reinterpret_cast(closure)); } } void grpc_call_combiner_stop(grpc_call_combiner* call_combiner DEBUG_ARGS, const char* reason) { GPR_TIMER_SCOPE("call_combiner_stop", 0); if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, "==> grpc_call_combiner_stop() [%p] [" DEBUG_FMT_STR "%s]", call_combiner DEBUG_FMT_ARGS, reason); } size_t prev_size = static_cast( gpr_atm_full_fetch_add(&call_combiner->size, (gpr_atm)-1)); if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " size: %" PRIdPTR " -> %" PRIdPTR, prev_size, prev_size - 1); } GPR_ASSERT(prev_size >= 1); if (prev_size > 1) { while (true) { if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " checking queue"); } bool empty; grpc_closure* closure = reinterpret_cast( gpr_mpscq_pop_and_check_end(&call_combiner->queue, &empty)); if (closure == nullptr) { // This can happen either due to a race condition within the mpscq // code or because of a race with grpc_call_combiner_start(). if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " queue returned no result; checking again"); } continue; } if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " EXECUTING FROM QUEUE: closure=%p error=%s", closure, grpc_error_string(closure->error_data.error)); } call_combiner_sched_closure(call_combiner, closure, closure->error_data.error); break; } } else if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, " queue empty"); } } void grpc_call_combiner_set_notify_on_cancel(grpc_call_combiner* call_combiner, grpc_closure* closure) { GRPC_STATS_INC_CALL_COMBINER_SET_NOTIFY_ON_CANCEL(); while (true) { // Decode original state. gpr_atm original_state = gpr_atm_acq_load(&call_combiner->cancel_state); grpc_error* original_error = decode_cancel_state_error(original_state); // If error is set, invoke the cancellation closure immediately. // Otherwise, store the new closure. if (original_error != GRPC_ERROR_NONE) { if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, "call_combiner=%p: scheduling notify_on_cancel callback=%p " "for pre-existing cancellation", call_combiner, closure); } GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_REF(original_error)); break; } else { if (gpr_atm_full_cas(&call_combiner->cancel_state, original_state, (gpr_atm)closure)) { if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, "call_combiner=%p: setting notify_on_cancel=%p", call_combiner, closure); } // If we replaced an earlier closure, invoke the original // closure with GRPC_ERROR_NONE. This allows callers to clean // up any resources they may be holding for the callback. if (original_state != 0) { closure = (grpc_closure*)original_state; if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, "call_combiner=%p: scheduling old cancel callback=%p", call_combiner, closure); } GRPC_CLOSURE_SCHED(closure, GRPC_ERROR_NONE); } break; } } // cas failed, try again. } } void grpc_call_combiner_cancel(grpc_call_combiner* call_combiner, grpc_error* error) { GRPC_STATS_INC_CALL_COMBINER_CANCELLED(); while (true) { gpr_atm original_state = gpr_atm_acq_load(&call_combiner->cancel_state); grpc_error* original_error = decode_cancel_state_error(original_state); if (original_error != GRPC_ERROR_NONE) { GRPC_ERROR_UNREF(error); break; } if (gpr_atm_full_cas(&call_combiner->cancel_state, original_state, encode_cancel_state_error(error))) { if (original_state != 0) { grpc_closure* notify_on_cancel = (grpc_closure*)original_state; if (grpc_call_combiner_trace.enabled()) { gpr_log(GPR_INFO, "call_combiner=%p: scheduling notify_on_cancel callback=%p", call_combiner, notify_on_cancel); } GRPC_CLOSURE_SCHED(notify_on_cancel, GRPC_ERROR_REF(error)); } break; } // cas failed, try again. } }