The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) https://grpc.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

482 lines
17 KiB

// Copyright 2023 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 "src/core/lib/promise/party.h"
#include <atomic>
#include <cstdint>
#include "absl/base/thread_annotations.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/str_format.h"
#include <grpc/support/port_platform.h>
#include "src/core/lib/event_engine/event_engine_context.h"
#include "src/core/lib/gprpp/sync.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/promise/activity.h"
#include "src/core/util/latent_see.h"
#ifdef GRPC_MAXIMIZE_THREADYNESS
#include "src/core/lib/gprpp/thd.h" // IWYU pragma: keep
#include "src/core/lib/iomgr/exec_ctx.h" // IWYU pragma: keep
#endif
namespace grpc_core {
///////////////////////////////////////////////////////////////////////////////
// PartySyncUsingAtomics
GRPC_MUST_USE_RESULT bool Party::RefIfNonZero() {
auto count = state_.load(std::memory_order_relaxed);
do {
// If zero, we are done (without an increment). If not, we must do a CAS
// to maintain the contract: do not increment the counter if it is already
// zero
if (count == 0) {
return false;
}
} while (!state_.compare_exchange_weak(count, count + kOneRef,
std::memory_order_acq_rel,
std::memory_order_relaxed));
LogStateChange("RefIfNonZero", count, count + kOneRef);
return true;
}
///////////////////////////////////////////////////////////////////////////////
// Party::Handle
// Weak handle to a Party.
// Handle can persist while Party goes away.
class Party::Handle final : public Wakeable {
public:
explicit Handle(Party* party) : party_(party) {}
// Ref the Handle (not the activity).
void Ref() { refs_.fetch_add(1, std::memory_order_relaxed); }
// Activity is going away... drop its reference and sever the connection back.
void DropActivity() ABSL_LOCKS_EXCLUDED(mu_) {
mu_.Lock();
CHECK_NE(party_, nullptr);
party_ = nullptr;
mu_.Unlock();
Unref();
}
void WakeupGeneric(WakeupMask wakeup_mask,
void (Party::*wakeup_method)(WakeupMask))
ABSL_LOCKS_EXCLUDED(mu_) {
mu_.Lock();
// Note that activity refcount can drop to zero, but we could win the lock
// against DropActivity, so we need to only increase activities refcount if
// it is non-zero.
Party* party = party_;
if (party != nullptr && party->RefIfNonZero()) {
mu_.Unlock();
// Activity still exists and we have a reference: wake it up, which will
// drop the ref.
(party->*wakeup_method)(wakeup_mask);
} else {
// Could not get the activity - it's either gone or going. No need to wake
// it up!
mu_.Unlock();
}
// Drop the ref to the handle (we have one ref = one wakeup semantics).
Unref();
}
// Activity needs to wake up (if it still exists!) - wake it up, and drop the
// ref that was kept for this handle.
void Wakeup(WakeupMask wakeup_mask) override ABSL_LOCKS_EXCLUDED(mu_) {
WakeupGeneric(wakeup_mask, &Party::Wakeup);
}
void WakeupAsync(WakeupMask wakeup_mask) override ABSL_LOCKS_EXCLUDED(mu_) {
WakeupGeneric(wakeup_mask, &Party::WakeupAsync);
}
void Drop(WakeupMask) override { Unref(); }
std::string ActivityDebugTag(WakeupMask) const override {
MutexLock lock(&mu_);
return party_ == nullptr ? "<unknown>" : party_->DebugTag();
}
private:
// Unref the Handle (not the activity).
void Unref() {
if (1 == refs_.fetch_sub(1, std::memory_order_acq_rel)) {
delete this;
}
}
// Two initial refs: one for the waiter that caused instantiation, one for the
// party.
std::atomic<size_t> refs_{2};
mutable Mutex mu_;
Party* party_ ABSL_GUARDED_BY(mu_);
};
Wakeable* Party::Participant::MakeNonOwningWakeable(Party* party) {
if (handle_ == nullptr) {
handle_ = new Handle(party);
return handle_;
}
handle_->Ref();
return handle_;
}
Party::Participant::~Participant() {
if (handle_ != nullptr) {
handle_->DropActivity();
}
}
Party::~Party() {}
void Party::CancelRemainingParticipants() {
if ((state_.load(std::memory_order_relaxed) & kAllocatedMask) == 0) return;
ScopedActivity activity(this);
promise_detail::Context<Arena> arena_ctx(arena_.get());
for (size_t i = 0; i < party_detail::kMaxParticipants; i++) {
if (auto* p =
participants_[i].exchange(nullptr, std::memory_order_acquire)) {
p->Destroy();
}
}
}
std::string Party::ActivityDebugTag(WakeupMask wakeup_mask) const {
return absl::StrFormat("%s [parts:%x]", DebugTag(), wakeup_mask);
}
Waker Party::MakeOwningWaker() {
DCHECK(currently_polling_ != kNotPolling);
IncrementRefCount();
return Waker(this, 1u << currently_polling_);
}
Waker Party::MakeNonOwningWaker() {
DCHECK(currently_polling_ != kNotPolling);
return Waker(participants_[currently_polling_]
.load(std::memory_order_relaxed)
->MakeNonOwningWakeable(this),
1u << currently_polling_);
}
void Party::ForceImmediateRepoll(WakeupMask mask) {
DCHECK(is_current());
wakeup_mask_ |= mask;
}
void Party::RunLockedAndUnref(Party* party, uint64_t prev_state) {
GRPC_LATENT_SEE_PARENT_SCOPE("Party::RunLocked");
#ifdef GRPC_MAXIMIZE_THREADYNESS
Thread thd(
"RunParty",
[party, prev_state]() {
ApplicationCallbackExecCtx app_exec_ctx;
ExecCtx exec_ctx;
party->RunPartyAndUnref(prev_state);
},
nullptr, Thread::Options().set_joinable(false));
thd.Start();
#else
struct RunState;
static thread_local RunState* g_run_state = nullptr;
struct PartyWakeup {
PartyWakeup() : party{nullptr} {}
PartyWakeup(Party* party, uint64_t prev_state)
: party{party}, prev_state{prev_state} {}
Party* party;
uint64_t prev_state;
};
struct RunState {
explicit RunState(PartyWakeup first) : first{first}, next{} {}
PartyWakeup first;
PartyWakeup next;
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION void Run() {
g_run_state = this;
do {
GRPC_LATENT_SEE_INNER_SCOPE("run_one_party");
first.party->RunPartyAndUnref(first.prev_state);
first = std::exchange(next, PartyWakeup{});
} while (first.party != nullptr);
DCHECK(g_run_state == this);
g_run_state = nullptr;
}
};
// If there is a party running, then we don't run it immediately
// but instead add it to the end of the list of parties to run.
// This enables a fairly straightforward batching of work from a
// call to a transport (or back again).
if (GPR_UNLIKELY(g_run_state != nullptr)) {
if (g_run_state->first.party == party) {
g_run_state->first.prev_state = prev_state;
party->Unref();
return;
}
if (g_run_state->next.party == party) {
g_run_state->next.prev_state = prev_state;
party->Unref();
return;
}
if (g_run_state->next.party != nullptr) {
// If there's already a different party queued, we're better off asking
// event engine to run it so we can spread load.
// We swap the oldest party to run on the event engine so that we don't
// accidentally end up with a tail latency problem whereby one party
// gets held for a really long time.
auto wakeup =
std::exchange(g_run_state->next, PartyWakeup{party, prev_state});
party->arena_->GetContext<grpc_event_engine::experimental::EventEngine>()
->Run([wakeup]() {
GRPC_LATENT_SEE_PARENT_SCOPE("Party::RunLocked offload");
ApplicationCallbackExecCtx app_exec_ctx;
ExecCtx exec_ctx;
RunState{wakeup}.Run();
});
return;
}
g_run_state->next = PartyWakeup{party, prev_state};
return;
}
RunState{{party, prev_state}}.Run();
#endif
}
void Party::RunPartyAndUnref(uint64_t prev_state) {
ScopedActivity activity(this);
promise_detail::Context<Arena> arena_ctx(arena_.get());
DCHECK_EQ(prev_state & kLocked, 0u)
<< "Party should be unlocked prior to first wakeup";
DCHECK_GE(prev_state & kRefMask, kOneRef);
// Now update prev_state to be what we want the CAS to see below.
DCHECK_EQ(prev_state & ~(kRefMask | kAllocatedMask), 0u)
<< "Party should have contained no wakeups on lock";
prev_state |= kLocked;
for (;;) {
uint64_t keep_allocated_mask = kAllocatedMask;
// For each wakeup bit...
while (wakeup_mask_ != 0) {
auto wakeup_mask = std::exchange(wakeup_mask_, 0);
while (wakeup_mask != 0) {
const uint64_t t = LowestOneBit(wakeup_mask);
const int i = absl::countr_zero(t);
wakeup_mask ^= t;
// If the participant is null, skip.
// This allows participants to complete whilst wakers still exist
// somewhere.
auto* participant = participants_[i].load(std::memory_order_acquire);
if (GPR_UNLIKELY(participant == nullptr)) {
GRPC_TRACE_LOG(promise_primitives, INFO)
<< "Party " << this << " Run:Wakeup " << i
<< " already complete";
continue;
}
GRPC_TRACE_LOG(promise_primitives, INFO)
<< "Party " << this << " Run:Wakeup " << i;
// Poll the participant.
currently_polling_ = i;
if (participant->PollParticipantPromise()) {
participants_[i].store(nullptr, std::memory_order_relaxed);
const uint64_t allocated_bit = (1u << i << kAllocatedShift);
keep_allocated_mask &= ~allocated_bit;
}
}
}
currently_polling_ = kNotPolling;
// Try to CAS the state we expected to have (with no wakeups or adds)
// back to unlocked (by masking in only the ref mask - sans locked bit).
// If this succeeds then no wakeups were added, no adds were added, and we
// have successfully unlocked.
// Otherwise, we need to loop again.
// Note that if an owning waker is created or the weak cas spuriously
// fails we will also loop again, but in that case see no wakeups or adds
// and so will get back here fairly quickly.
// TODO(ctiller): consider mitigations for the accidental wakeup on owning
// waker creation case -- I currently expect this will be more expensive
// than this quick loop.
if (state_.compare_exchange_weak(
prev_state,
(prev_state & (kRefMask | keep_allocated_mask)) - kOneRef,
std::memory_order_acq_rel, std::memory_order_acquire)) {
LogStateChange("Run:End", prev_state,
prev_state & (kRefMask | kAllocatedMask) - kOneRef);
if ((prev_state & kRefMask) == kOneRef) {
// We're done with the party.
PartyIsOver();
}
return;
}
// CAS out (but retrieve) any allocations and wakeups that occurred during
// the run.
while (!state_.compare_exchange_weak(
prev_state, prev_state & (kRefMask | kLocked | keep_allocated_mask))) {
// Nothing to do here.
}
LogStateChange("Run:Continue", prev_state,
prev_state & (kRefMask | kLocked | keep_allocated_mask));
DCHECK(prev_state & kLocked)
<< "Party should be locked; prev_state=" << prev_state;
DCHECK_GE(prev_state & kRefMask, kOneRef);
// From the previous state, extract which participants we're to wakeup.
wakeup_mask_ |= prev_state & kWakeupMask;
// Now update prev_state to be what we want the CAS to see once wakeups
// complete next iteration.
prev_state &= kRefMask | kLocked | keep_allocated_mask;
}
}
void Party::AddParticipants(Participant** participants, size_t count) {
uint64_t state = state_.load(std::memory_order_acquire);
uint64_t allocated;
size_t slots[party_detail::kMaxParticipants];
// Find slots for each new participant, ordering them from lowest available
// slot upwards to ensure the same poll ordering as presentation ordering to
// this function.
WakeupMask wakeup_mask;
uint64_t new_state;
do {
wakeup_mask = 0;
allocated = (state & kAllocatedMask) >> kAllocatedShift;
for (size_t i = 0; i < count; i++) {
auto new_mask = LowestOneBit(~allocated);
if (GPR_UNLIKELY((new_mask & kWakeupMask) == 0)) {
DelayAddParticipants(participants, count);
return;
}
wakeup_mask |= new_mask;
allocated |= new_mask;
slots[i] = absl::countr_zero(new_mask);
}
// Try to allocate this slot and take a ref (atomically).
// Ref needs to be taken because once we store the participant it could be
// spuriously woken up and unref the party.
new_state = (state | (allocated << kAllocatedShift)) + kOneRef;
} while (!state_.compare_exchange_weak(
state, new_state, std::memory_order_acq_rel, std::memory_order_acquire));
LogStateChange("AddParticipantsAndRef", state, new_state);
for (size_t i = 0; i < count; i++) {
GRPC_TRACE_LOG(party_state, INFO)
<< "Party " << this << " AddParticipant: " << slots[i]
<< " " << participants[i];
participants_[slots[i]].store(participants[i], std::memory_order_release);
}
// Now we need to wake up the party.
WakeupFromState(new_state, wakeup_mask);
}
void Party::AddParticipant(Participant* participant) {
uint64_t state = state_.load(std::memory_order_acquire);
uint64_t allocated;
size_t slot;
// Find slots for each new participant, ordering them from lowest available
// slot upwards to ensure the same poll ordering as presentation ordering to
// this function.
uint64_t wakeup_mask;
uint64_t new_state;
do {
allocated = (state & kAllocatedMask) >> kAllocatedShift;
wakeup_mask = LowestOneBit(~allocated);
if (GPR_UNLIKELY((wakeup_mask & kWakeupMask) == 0)) {
DelayAddParticipants(&participant, 1);
return;
}
DCHECK_NE(wakeup_mask & kWakeupMask, 0u)
<< "No available slots for new participant; allocated=" << allocated
<< " state=" << state << " wakeup_mask=" << wakeup_mask;
allocated |= wakeup_mask;
slot = absl::countr_zero(wakeup_mask);
// Try to allocate this slot and take a ref (atomically).
// Ref needs to be taken because once we store the participant it could be
// spuriously woken up and unref the party.
new_state = (state | (allocated << kAllocatedShift)) + kOneRef;
} while (!state_.compare_exchange_weak(
state, new_state, std::memory_order_acq_rel, std::memory_order_acquire));
LogStateChange("AddParticipantsAndRef", state, new_state);
GRPC_TRACE_LOG(party_state, INFO)
<< "Party " << this << " AddParticipant: " << slot
<< " [participant=" << participant << "]";
participants_[slot].store(participant, std::memory_order_release);
// Now we need to wake up the party.
WakeupFromState(new_state, wakeup_mask);
}
void Party::DelayAddParticipants(Participant** participants, size_t count) {
// We need to delay the addition of participants.
IncrementRefCount();
VLOG_EVERY_N_SEC(2, 10) << "Delaying addition of " << count
<< " participants to party " << this
<< " because it is full.";
std::vector<Participant*> delayed_participants{participants,
participants + count};
arena_->GetContext<grpc_event_engine::experimental::EventEngine>()->Run(
[this, delayed_participants = std::move(delayed_participants)]() mutable {
ApplicationCallbackExecCtx app_exec_ctx;
ExecCtx exec_ctx;
AddParticipants(delayed_participants.data(),
delayed_participants.size());
Unref();
});
}
void Party::WakeupAsync(WakeupMask wakeup_mask) {
// Or in the wakeup bit for the participant, AND the locked bit.
uint64_t prev_state = state_.load(std::memory_order_relaxed);
LogStateChange("ScheduleWakeup", prev_state,
prev_state | (wakeup_mask & kWakeupMask) | kLocked);
while (true) {
if ((prev_state & kLocked) == 0) {
if (state_.compare_exchange_weak(prev_state, prev_state | kLocked,
std::memory_order_acq_rel,
std::memory_order_acquire)) {
LogStateChange("WakeupAsync", prev_state, prev_state | kLocked);
wakeup_mask_ |= wakeup_mask;
arena_->GetContext<grpc_event_engine::experimental::EventEngine>()->Run(
[this, prev_state]() {
ApplicationCallbackExecCtx app_exec_ctx;
ExecCtx exec_ctx;
RunLockedAndUnref(this, prev_state);
});
return;
}
} else {
if (state_.compare_exchange_weak(
prev_state, (prev_state | wakeup_mask) - kOneRef,
std::memory_order_acq_rel, std::memory_order_acquire)) {
LogStateChange("WakeupAsync", prev_state, prev_state | wakeup_mask);
return;
}
}
}
}
void Party::Drop(WakeupMask) { Unref(); }
void Party::PartyIsOver() {
CancelRemainingParticipants();
auto arena = std::move(arena_);
this->~Party();
}
} // namespace grpc_core