File: tracked_ref_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (149 lines) | stat: -rw-r--r-- 4,969 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/task/thread_pool/tracked_ref.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/synchronization/atomic_flag.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base::internal {

namespace {

class ObjectWithTrackedRefs {
 public:
  ObjectWithTrackedRefs() : tracked_ref_factory_(this) {}
  ObjectWithTrackedRefs(const ObjectWithTrackedRefs&) = delete;
  ObjectWithTrackedRefs& operator=(const ObjectWithTrackedRefs&) = delete;
  ~ObjectWithTrackedRefs() { under_destruction_.Set(); }

  TrackedRef<ObjectWithTrackedRefs> GetTrackedRef() {
    return tracked_ref_factory_.GetTrackedRef();
  }

  bool under_destruction() const { return under_destruction_.IsSet(); }

 private:
  // True once ~ObjectWithTrackedRefs() has been initiated.
  AtomicFlag under_destruction_;

  TrackedRefFactory<ObjectWithTrackedRefs> tracked_ref_factory_;
};

}  // namespace

// Test that an object with a TrackedRefFactory can be destroyed by a single
// owner but that its destruction will be blocked on the TrackedRefs being
// released.
TEST(TrackedRefTest, TrackedRefObjectDeletion) {
  Thread thread("TrackedRefTestThread");
  thread.Start();

  std::unique_ptr<ObjectWithTrackedRefs> obj =
      std::make_unique<ObjectWithTrackedRefs>();

  TimeTicks begin = TimeTicks::Now();

  thread.task_runner()->PostDelayedTask(
      FROM_HERE,
      BindOnce(
          [](TrackedRef<ObjectWithTrackedRefs> obj) {
            // By the time this kicks in, the object should already be under
            // destruction, but blocked on this TrackedRef being released. This
            // is technically racy (main thread has to run |obj.reset()| and
            // this thread has to observe the side-effects before this delayed
            // task fires). If this ever flakes this expectation could be turned
            // into a while(!obj->under_destruction()); but until that's proven
            // flaky in practice, this expectation is more readable and
            // diagnosable then a hang.
            EXPECT_TRUE(obj->under_destruction());
          },
          obj->GetTrackedRef()),
      TestTimeouts::tiny_timeout());

  // This should kick off destruction but block until the above task resolves
  // and releases the TrackedRef.
  obj.reset();
  EXPECT_GE(TimeTicks::Now() - begin, TestTimeouts::tiny_timeout());
}

TEST(TrackedRefTest, ManyThreadsRacing) {
  constexpr int kNumThreads = 16;
  std::vector<std::unique_ptr<Thread>> threads;
  for (int i = 0; i < kNumThreads; ++i) {
    threads.push_back(std::make_unique<Thread>("TrackedRefTestThread"));
    threads.back()->StartAndWaitForTesting();
  }

  std::unique_ptr<ObjectWithTrackedRefs> obj =
      std::make_unique<ObjectWithTrackedRefs>();

  // Send a TrackedRef to each thread.
  for (auto& thread : threads) {
    thread->task_runner()->PostTask(
        FROM_HERE, BindOnce(
                       [](TrackedRef<ObjectWithTrackedRefs> obj) {
                         // Confirm it's still safe to
                         // dereference |obj| (and, bonus, that
                         // playing with TrackedRefs some more
                         // isn't problematic).
                         EXPECT_TRUE(obj->GetTrackedRef());
                       },
                       obj->GetTrackedRef()));
  }

  // Initiate destruction racily with the above tasks' execution (they will
  // crash if TrackedRefs aren't WAI).
  obj.reset();
}

// Test that instantiating and deleting a TrackedRefFactory without ever taking
// a TrackedRef on it is fine.
TEST(TrackedRefTest, NoTrackedRefs) {
  ObjectWithTrackedRefs obj;
}

namespace {
void ConsumesTrackedRef(TrackedRef<ObjectWithTrackedRefs> obj) {}
}  // namespace

// Test that destroying a TrackedRefFactory which had TrackedRefs in the past
// that are already gone is WAI.
TEST(TrackedRefTest, NoPendingTrackedRefs) {
  ObjectWithTrackedRefs obj;
  ConsumesTrackedRef(obj.GetTrackedRef());
}

TEST(TrackedRefTest, CopyAndMoveSemantics) {
  struct Foo {
    Foo() : factory(this) {}
    TrackedRefFactory<Foo> factory;
  };
  Foo foo;

  EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());

  {
    TrackedRef<Foo> plain = foo.factory.GetTrackedRef();
    EXPECT_EQ(2, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());

    TrackedRef<Foo> copy_constructed(plain);
    EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());

    TrackedRef<Foo> moved_constructed(std::move(copy_constructed));
    EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
  }

  EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
}

}  // namespace base::internal