blob: 5217bfa10674255d1c388c6849e87d3a45808a13 [file] [log] [blame]
//
// Copyright 2020 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.
//
#ifndef GRPC_CORE_LIB_GPRPP_DUAL_REF_COUNTED_H
#define GRPC_CORE_LIB_GPRPP_DUAL_REF_COUNTED_H
#include <grpc/support/port_platform.h>
#include <grpc/support/atm.h>
#include <grpc/support/log.h>
#include <grpc/support/sync.h>
#include <atomic>
#include <cassert>
#include <cinttypes>
#include "src/core/lib/gprpp/atomic.h"
#include "src/core/lib/gprpp/debug_location.h"
#include "src/core/lib/gprpp/orphanable.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
namespace grpc_core {
// DualRefCounted is an interface for reference-counted objects with two
// classes of refs: strong refs (usually just called "refs") and weak refs.
// This supports cases where an object needs to start shutting down when
// all external callers are done with it (represented by strong refs) but
// cannot be destroyed until all internal callbacks are complete
// (represented by weak refs).
//
// Each class of refs can be incremented and decremented independently.
// Objects start with 1 strong ref and 0 weak refs at instantiation.
// When the strong refcount reaches 0, the object's Orphan() method is called.
// When the weak refcount reaches 0, the object is destroyed.
//
// This will be used by CRTP (curiously-recurring template pattern), e.g.:
// class MyClass : public RefCounted<MyClass> { ... };
template <typename Child>
class DualRefCounted : public Orphanable {
public:
~DualRefCounted() override = default;
RefCountedPtr<Child> Ref() GRPC_MUST_USE_RESULT {
IncrementRefCount();
return RefCountedPtr<Child>(static_cast<Child*>(this));
}
RefCountedPtr<Child> Ref(const DebugLocation& location,
const char* reason) GRPC_MUST_USE_RESULT {
IncrementRefCount(location, reason);
return RefCountedPtr<Child>(static_cast<Child*>(this));
}
void Unref() {
// Convert strong ref to weak ref.
const uint64_t prev_ref_pair =
refs_.FetchAdd(MakeRefPair(-1, 1), MemoryOrder::ACQ_REL);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
#ifndef NDEBUG
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p unref %d -> %d, weak_ref %d -> %d", trace_, this,
strong_refs, strong_refs - 1, weak_refs, weak_refs + 1);
}
GPR_ASSERT(strong_refs > 0);
#endif
if (GPR_UNLIKELY(strong_refs == 1)) {
Orphan();
}
// Now drop the weak ref.
WeakUnref();
}
void Unref(const DebugLocation& location, const char* reason) {
const uint64_t prev_ref_pair =
refs_.FetchAdd(MakeRefPair(-1, 1), MemoryOrder::ACQ_REL);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
#ifndef NDEBUG
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p %s:%d unref %d -> %d, weak_ref %d -> %d) %s",
trace_, this, location.file(), location.line(), strong_refs,
strong_refs - 1, weak_refs, weak_refs + 1, reason);
}
GPR_ASSERT(strong_refs > 0);
#else
// Avoid unused-parameter warnings for debug-only parameters
(void)location;
(void)reason;
#endif
if (GPR_UNLIKELY(strong_refs == 1)) {
Orphan();
}
// Now drop the weak ref.
WeakUnref(location, reason);
}
RefCountedPtr<Child> RefIfNonZero() GRPC_MUST_USE_RESULT {
uint64_t prev_ref_pair = refs_.Load(MemoryOrder::ACQUIRE);
do {
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
#ifndef NDEBUG
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p ref_if_non_zero %d -> %d (weak_refs=%d)",
trace_, this, strong_refs, strong_refs + 1, weak_refs);
}
#endif
if (strong_refs == 0) return nullptr;
} while (!refs_.CompareExchangeWeak(
&prev_ref_pair, prev_ref_pair + MakeRefPair(1, 0), MemoryOrder::ACQ_REL,
MemoryOrder::ACQUIRE));
return RefCountedPtr<Child>(static_cast<Child*>(this));
}
RefCountedPtr<Child> RefIfNonZero(const DebugLocation& location,
const char* reason) GRPC_MUST_USE_RESULT {
uint64_t prev_ref_pair = refs_.Load(MemoryOrder::ACQUIRE);
do {
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
#ifndef NDEBUG
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
if (trace_ != nullptr) {
gpr_log(GPR_INFO,
"%s:%p %s:%d ref_if_non_zero %d -> %d (weak_refs=%d) %s",
trace_, this, location.file(), location.line(), strong_refs,
strong_refs + 1, weak_refs, reason);
}
#else
// Avoid unused-parameter warnings for debug-only parameters
(void)location;
(void)reason;
#endif
if (strong_refs == 0) return nullptr;
} while (!refs_.CompareExchangeWeak(
&prev_ref_pair, prev_ref_pair + MakeRefPair(1, 0), MemoryOrder::ACQ_REL,
MemoryOrder::ACQUIRE));
return RefCountedPtr<Child>(static_cast<Child*>(this));
}
WeakRefCountedPtr<Child> WeakRef() GRPC_MUST_USE_RESULT {
IncrementWeakRefCount();
return WeakRefCountedPtr<Child>(static_cast<Child*>(this));
}
WeakRefCountedPtr<Child> WeakRef(const DebugLocation& location,
const char* reason) GRPC_MUST_USE_RESULT {
IncrementWeakRefCount(location, reason);
return WeakRefCountedPtr<Child>(static_cast<Child*>(this));
}
void WeakUnref() {
#ifndef NDEBUG
// Grab a copy of the trace flag before the atomic change, since we
// will no longer be holding a ref afterwards and therefore can't
// safely access it, since another thread might free us in the interim.
const char* trace = trace_;
#endif
const uint64_t prev_ref_pair =
refs_.FetchSub(MakeRefPair(0, 1), MemoryOrder::ACQ_REL);
#ifndef NDEBUG
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
if (trace != nullptr) {
gpr_log(GPR_INFO, "%s:%p weak_unref %d -> %d (refs=%d)", trace, this,
weak_refs, weak_refs - 1, strong_refs);
}
GPR_ASSERT(weak_refs > 0);
#endif
if (GPR_UNLIKELY(prev_ref_pair == MakeRefPair(0, 1))) {
delete static_cast<Child*>(this);
}
}
void WeakUnref(const DebugLocation& location, const char* reason) {
#ifndef NDEBUG
// Grab a copy of the trace flag before the atomic change, since we
// will no longer be holding a ref afterwards and therefore can't
// safely access it, since another thread might free us in the interim.
const char* trace = trace_;
#endif
const uint64_t prev_ref_pair =
refs_.FetchSub(MakeRefPair(0, 1), MemoryOrder::ACQ_REL);
#ifndef NDEBUG
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
if (trace != nullptr) {
gpr_log(GPR_INFO, "%s:%p %s:%d weak_unref %d -> %d (refs=%d) %s", trace,
this, location.file(), location.line(), weak_refs, weak_refs - 1,
strong_refs, reason);
}
GPR_ASSERT(weak_refs > 0);
#else
// Avoid unused-parameter warnings for debug-only parameters
(void)location;
(void)reason;
#endif
if (GPR_UNLIKELY(prev_ref_pair == MakeRefPair(0, 1))) {
delete static_cast<Child*>(this);
}
}
// Not copyable nor movable.
DualRefCounted(const DualRefCounted&) = delete;
DualRefCounted& operator=(const DualRefCounted&) = delete;
protected:
// Note: Tracing is a no-op in non-debug builds.
explicit DualRefCounted(
const char*
#ifndef NDEBUG
// Leave unnamed if NDEBUG to avoid unused parameter warning
trace
#endif
= nullptr,
int32_t initial_refcount = 1)
:
#ifndef NDEBUG
trace_(trace),
#endif
refs_(MakeRefPair(initial_refcount, 0)) {
}
private:
// Allow RefCountedPtr<> to access IncrementRefCount().
template <typename T>
friend class RefCountedPtr;
// Allow WeakRefCountedPtr<> to access IncrementWeakRefCount().
template <typename T>
friend class WeakRefCountedPtr;
// First 32 bits are strong refs, next 32 bits are weak refs.
static uint64_t MakeRefPair(uint32_t strong, uint32_t weak) {
return (static_cast<uint64_t>(strong) << 32) + static_cast<int64_t>(weak);
}
static uint32_t GetStrongRefs(uint64_t ref_pair) {
return static_cast<uint32_t>(ref_pair >> 32);
}
static uint32_t GetWeakRefs(uint64_t ref_pair) {
return static_cast<uint32_t>(ref_pair & 0xffffffffu);
}
void IncrementRefCount() {
#ifndef NDEBUG
const uint64_t prev_ref_pair =
refs_.FetchAdd(MakeRefPair(1, 0), MemoryOrder::RELAXED);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
GPR_ASSERT(strong_refs != 0);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p ref %d -> %d; (weak_refs=%d)", trace_, this,
strong_refs, strong_refs + 1, weak_refs);
}
#else
refs_.FetchAdd(MakeRefPair(1, 0), MemoryOrder::RELAXED);
#endif
}
void IncrementRefCount(const DebugLocation& location, const char* reason) {
#ifndef NDEBUG
const uint64_t prev_ref_pair =
refs_.FetchAdd(MakeRefPair(1, 0), MemoryOrder::RELAXED);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
GPR_ASSERT(strong_refs != 0);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p %s:%d ref %d -> %d (weak_refs=%d) %s", trace_,
this, location.file(), location.line(), strong_refs,
strong_refs + 1, weak_refs, reason);
}
#else
// Use conditionally-important parameters
(void)location;
(void)reason;
refs_.FetchAdd(MakeRefPair(1, 0), MemoryOrder::RELAXED);
#endif
}
void IncrementWeakRefCount() {
#ifndef NDEBUG
const uint64_t prev_ref_pair =
refs_.FetchAdd(MakeRefPair(0, 1), MemoryOrder::RELAXED);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p weak_ref %d -> %d; (refs=%d)", trace_, this,
weak_refs, weak_refs + 1, strong_refs);
}
#else
refs_.FetchAdd(MakeRefPair(0, 1), MemoryOrder::RELAXED);
#endif
}
void IncrementWeakRefCount(const DebugLocation& location,
const char* reason) {
#ifndef NDEBUG
const uint64_t prev_ref_pair =
refs_.FetchAdd(MakeRefPair(0, 1), MemoryOrder::RELAXED);
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair);
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair);
if (trace_ != nullptr) {
gpr_log(GPR_INFO, "%s:%p %s:%d weak_ref %d -> %d (refs=%d) %s", trace_,
this, location.file(), location.line(), weak_refs, weak_refs + 1,
strong_refs, reason);
}
#else
// Use conditionally-important parameters
(void)location;
(void)reason;
refs_.FetchAdd(MakeRefPair(0, 1), MemoryOrder::RELAXED);
#endif
}
#ifndef NDEBUG
const char* trace_;
#endif
Atomic<uint64_t> refs_;
};
} // namespace grpc_core
#endif /* GRPC_CORE_LIB_GPRPP_DUAL_REF_COUNTED_H */