mirror of https://github.com/grpc/grpc.git
parent
303ce9ea3d
commit
98841a990d
9 changed files with 975 additions and 5 deletions
@ -0,0 +1,336 @@ |
||||
//
|
||||
// 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/debug/trace.h" |
||||
#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: |
||||
virtual ~DualRefCounted() = 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p unref %d -> %d, weak_ref %d -> %d", |
||||
trace_flag_->name(), 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p %s:%d unref %d -> %d, weak_ref %d -> %d) %s", |
||||
trace_flag_->name(), 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p ref_if_non_zero %d -> %d (weak_refs=%d)", |
||||
trace_flag_->name(), 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, |
||||
"%s:%p %s:%d ref_if_non_zero %d -> %d (weak_refs=%d) %s", |
||||
trace_flag_->name(), 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
|
||||
// can't safely access it afterwards if we're going to be freed.
|
||||
auto* trace_flag = trace_flag_; |
||||
#endif |
||||
const uint64_t prev_ref_pair = |
||||
refs_.FetchSub(MakeRefPair(0, 1), MemoryOrder::ACQ_REL); |
||||
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair); |
||||
#ifndef NDEBUG |
||||
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair); |
||||
if (trace_flag != nullptr && trace_flag->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p weak_unref %d -> %d (refs=%d)", |
||||
trace_flag->name(), 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
|
||||
// can't safely access it afterwards if we're going to be freed.
|
||||
auto* trace_flag = trace_flag_; |
||||
#endif |
||||
const uint64_t prev_ref_pair = |
||||
refs_.FetchSub(MakeRefPair(0, 1), MemoryOrder::ACQ_REL); |
||||
const uint32_t weak_refs = GetWeakRefs(prev_ref_pair); |
||||
#ifndef NDEBUG |
||||
const uint32_t strong_refs = GetStrongRefs(prev_ref_pair); |
||||
if (trace_flag != nullptr && trace_flag->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p %s:%d weak_unref %d -> %d (refs=%d) %s", |
||||
trace_flag->name(), 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: |
||||
// TraceFlagT is defined to accept both DebugOnlyTraceFlag and TraceFlag.
|
||||
// Note: RefCount tracing is only enabled on debug builds, even when a
|
||||
// TraceFlag is used.
|
||||
template <typename TraceFlagT = TraceFlag> |
||||
explicit DualRefCounted( |
||||
TraceFlagT* |
||||
#ifndef NDEBUG |
||||
// Leave unnamed if NDEBUG to avoid unused parameter warning
|
||||
trace_flag |
||||
#endif |
||||
= nullptr, |
||||
int32_t initial_refcount = 1) |
||||
: |
||||
#ifndef NDEBUG |
||||
trace_flag_(trace_flag), |
||||
#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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p ref %d -> %d; (weak_refs=%d)", |
||||
trace_flag_->name(), 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p %s:%d ref %d -> %d (weak_refs=%d) %s", |
||||
trace_flag_->name(), 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p weak_ref %d -> %d; (refs=%d)", |
||||
trace_flag_->name(), 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_flag_ != nullptr && trace_flag_->enabled()) { |
||||
gpr_log(GPR_INFO, "%s:%p %s:%d weak_ref %d -> %d (refs=%d) %s", |
||||
trace_flag_->name(), 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 |
||||
TraceFlag* trace_flag_; |
||||
#endif |
||||
Atomic<uint64_t> refs_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_LIB_GPRPP_DUAL_REF_COUNTED_H */ |
@ -0,0 +1,112 @@ |
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#include "src/core/lib/gprpp/dual_ref_counted.h" |
||||
|
||||
#include <set> |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
#include "test/core/util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
namespace { |
||||
|
||||
class Foo : public DualRefCounted<Foo> { |
||||
public: |
||||
Foo() = default; |
||||
~Foo() { GPR_ASSERT(shutting_down_); } |
||||
|
||||
void Orphan() override { shutting_down_ = true; } |
||||
|
||||
private: |
||||
bool shutting_down_ = false; |
||||
}; |
||||
|
||||
TEST(DualRefCounted, Basic) { |
||||
Foo* foo = new Foo(); |
||||
foo->Unref(); |
||||
} |
||||
|
||||
TEST(DualRefCounted, ExtraRef) { |
||||
Foo* foo = new Foo(); |
||||
foo->Ref().release(); |
||||
foo->Unref(); |
||||
foo->Unref(); |
||||
} |
||||
|
||||
TEST(DualRefCounted, ExtraWeakRef) { |
||||
Foo* foo = new Foo(); |
||||
foo->WeakRef().release(); |
||||
foo->Unref(); |
||||
foo->WeakUnref(); |
||||
} |
||||
|
||||
TEST(DualRefCounted, RefIfNonZero) { |
||||
Foo* foo = new Foo(); |
||||
foo->WeakRef().release(); |
||||
{ |
||||
RefCountedPtr<Foo> foop = foo->RefIfNonZero(); |
||||
EXPECT_NE(foop.get(), nullptr); |
||||
} |
||||
foo->Unref(); |
||||
{ |
||||
RefCountedPtr<Foo> foop = foo->RefIfNonZero(); |
||||
EXPECT_EQ(foop.get(), nullptr); |
||||
} |
||||
foo->WeakUnref(); |
||||
} |
||||
|
||||
// Note: We use DebugOnlyTraceFlag instead of TraceFlag to ensure that
|
||||
// things build properly in both debug and non-debug cases.
|
||||
DebugOnlyTraceFlag foo_tracer(true, "foo"); |
||||
|
||||
class FooWithTracing : public DualRefCounted<FooWithTracing> { |
||||
public: |
||||
FooWithTracing() : DualRefCounted(&foo_tracer) {} |
||||
~FooWithTracing() { GPR_ASSERT(shutting_down_); } |
||||
|
||||
void Orphan() override { shutting_down_ = true; } |
||||
|
||||
private: |
||||
bool shutting_down_ = false; |
||||
}; |
||||
|
||||
TEST(DualRefCountedWithTracing, Basic) { |
||||
FooWithTracing* foo = new FooWithTracing(); |
||||
foo->Ref(DEBUG_LOCATION, "extra_ref").release(); |
||||
foo->Unref(DEBUG_LOCATION, "extra_ref"); |
||||
foo->WeakRef(DEBUG_LOCATION, "extra_ref").release(); |
||||
foo->WeakUnref(DEBUG_LOCATION, "extra_ref"); |
||||
// Can use the no-argument methods, too.
|
||||
foo->Ref().release(); |
||||
foo->Unref(); |
||||
foo->WeakRef().release(); |
||||
foo->WeakUnref(); |
||||
foo->Unref(DEBUG_LOCATION, "original_ref"); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
grpc::testing::TestEnvironment env(argc, argv); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue