Files
paddlepaddle--paddle/test/cpp/compat/c10_intrusive_ptr_lifecycle_test.cc
2026-07-13 12:40:42 +08:00

184 lines
6.2 KiB
C++

// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// 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 <c10/util/intrusive_ptr.h>
#include <atomic>
#include "gtest/gtest.h"
// ============================================================================
// Helper: a minimal intrusive_ptr_target subclass that tracks destruction.
// ============================================================================
namespace {
class TestTarget : public c10::intrusive_ptr_target {
public:
explicit TestTarget(std::atomic<int>* destroy_count)
: destroy_count_(destroy_count) {}
~TestTarget() override {
destroy_count_->fetch_add(1, std::memory_order_relaxed);
}
private:
std::atomic<int>* destroy_count_;
};
} // namespace
// ============================================================================
// Weak reference lifecycle tests
// ============================================================================
// When a weak_intrusive_ptr outlives an intrusive_ptr, the object must NOT be
// deleted until the weak_intrusive_ptr is also destroyed.
TEST(IntrusivePtrLifecycleTest, WeakPtrKeepsObjectAliveAfterStrongReset) {
std::atomic<int> destroy_count{0};
c10::intrusive_ptr<TestTarget> strong =
c10::make_intrusive<TestTarget>(&destroy_count);
c10::weak_intrusive_ptr<TestTarget> weak(strong);
// Object should be alive with both strong and weak references.
ASSERT_EQ(destroy_count.load(), 0);
ASSERT_EQ(strong.use_count(), 1u);
// Destroy the strong reference.
strong.reset();
// The object must NOT have been deleted yet: the weak reference keeps it.
ASSERT_EQ(destroy_count.load(), 0);
// lock() should return an empty intrusive_ptr (strong count is 0).
c10::intrusive_ptr<TestTarget> locked = weak.lock();
ASSERT_FALSE(locked.defined());
// expired() should be true.
ASSERT_TRUE(weak.expired());
// Destroying the weak reference should trigger deletion.
weak.reset();
ASSERT_EQ(destroy_count.load(), 1);
}
// When all strong references are released and no weak references exist,
// the object is deleted immediately.
TEST(IntrusivePtrLifecycleTest, ObjectDeletedImmediatelyWithNoWeakRefs) {
std::atomic<int> destroy_count{0};
{
c10::intrusive_ptr<TestTarget> strong =
c10::make_intrusive<TestTarget>(&destroy_count);
// No weak references created.
ASSERT_EQ(destroy_count.load(), 0);
} // strong goes out of scope here.
// Without any weak references, the object should be deleted immediately.
ASSERT_EQ(destroy_count.load(), 1);
}
// lock() must return empty when the strong reference count is zero, and
// must not resurrect the object.
TEST(IntrusivePtrLifecycleTest, LockReturnsEmptyAfterStrongGone) {
std::atomic<int> destroy_count{0};
c10::intrusive_ptr<TestTarget> strong =
c10::make_intrusive<TestTarget>(&destroy_count);
c10::weak_intrusive_ptr<TestTarget> weak(strong);
strong.reset(); // Kill the strong reference.
// lock() must return an empty (undefined) intrusive_ptr.
c10::intrusive_ptr<TestTarget> result = weak.lock();
ASSERT_FALSE(result.defined());
ASSERT_EQ(result.get(), nullptr);
// Object is still alive (weak reference holds it).
ASSERT_EQ(destroy_count.load(), 0);
}
// Multiple weak references: object survives until the LAST weak ref is gone.
TEST(IntrusivePtrLifecycleTest, ObjectSurvivesUntilLastWeakRefGone) {
std::atomic<int> destroy_count{0};
c10::intrusive_ptr<TestTarget> strong =
c10::make_intrusive<TestTarget>(&destroy_count);
c10::weak_intrusive_ptr<TestTarget> weak1(strong);
c10::weak_intrusive_ptr<TestTarget> weak2(strong);
strong.reset(); // Strong gone; two weak refs remain.
ASSERT_EQ(destroy_count.load(), 0);
weak1.reset(); // First weak gone; one weak ref remains.
ASSERT_EQ(destroy_count.load(), 0);
weak2.reset(); // Last weak gone; object should be deleted now.
ASSERT_EQ(destroy_count.load(), 1);
}
// Resetting multiple copies of a strong intrusive_ptr should not double-delete.
TEST(IntrusivePtrLifecycleTest, MultipleCopiesNoDoubleFree) {
std::atomic<int> destroy_count{0};
c10::intrusive_ptr<TestTarget> a =
c10::make_intrusive<TestTarget>(&destroy_count);
c10::intrusive_ptr<TestTarget> b = a; // copy: refcount = 2
c10::intrusive_ptr<TestTarget> c = a; // copy: refcount = 3
c10::weak_intrusive_ptr<TestTarget> weak(a);
ASSERT_EQ(a.use_count(), 3u);
a.reset();
ASSERT_EQ(destroy_count.load(), 0);
b.reset();
ASSERT_EQ(destroy_count.load(), 0);
c.reset(); // Strong count drops to zero; weak ref still alive.
ASSERT_EQ(destroy_count.load(), 0); // Not deleted yet.
weak.reset(); // Last reference; object deleted.
ASSERT_EQ(destroy_count.load(), 1);
}
// raw::intrusive_ptr::incref/decref should follow the same two-phase lifecycle.
TEST(IntrusivePtrLifecycleTest, RawIncrefDecrefTwoPhaseLifecycle) {
std::atomic<int> destroy_count{0};
// Create via make_intrusive (strong=1, weak=1).
c10::intrusive_ptr<TestTarget> strong =
c10::make_intrusive<TestTarget>(&destroy_count);
TestTarget* raw = strong.get();
// Create a weak reference.
c10::weak_intrusive_ptr<TestTarget> weak(strong);
// Manually add a strong reference via raw API, then decref via raw API.
c10::raw::intrusive_ptr::incref(raw); // strong = 2
strong.reset(); // strong = 1 via reset_()
ASSERT_EQ(destroy_count.load(), 0); // Still alive.
// Decref the raw strong reference to zero.
c10::raw::intrusive_ptr::decref(raw); // strong = 0; implicit weak released.
// Object is not yet deleted because weak reference still exists.
ASSERT_EQ(destroy_count.load(), 0);
weak.reset(); // Weak reference gone; object deleted.
ASSERT_EQ(destroy_count.load(), 1);
}