v8 150.2.0

Rust bindings to V8
Documentation
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "partition_alloc/partition_lock.h"

#include <algorithm>
#include <atomic>
#include <cstddef>

#include "partition_alloc/partition_alloc_base/compiler_specific.h"

#if PA_BUILDFLAG(IS_POSIX)
#include <sys/resource.h>
#include <sys/utsname.h>
#endif

#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/partition_alloc_base/rand_util.h"
#include "partition_alloc/partition_alloc_base/thread_annotations.h"
#include "partition_alloc/partition_alloc_base/threading/platform_thread_for_testing.h"
#include "partition_alloc/partition_alloc_base/time/time.h"
#include "partition_alloc/partition_root.h"
#include "partition_alloc/spinning_mutex.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace partition_alloc::internal {

TEST(PartitionAllocLockTest, Simple) {
  Lock lock;
  lock.Acquire();
  lock.Release();
}

namespace {

Lock g_lock;

}  // namespace

TEST(PartitionAllocLockTest, StaticLockStartsUnlocked) {
  g_lock.Acquire();
  g_lock.Release();
}

namespace {

class ThreadDelegateForContended
    : public base::PlatformThreadForTesting::Delegate {
 public:
  explicit ThreadDelegateForContended(Lock& start_lock,
                                      Lock& lock,
                                      int iterations,
                                      int& counter)
      : start_lock_(start_lock),
        lock_(lock),
        iterations_(iterations),
        counter_(counter) {}

  void ThreadMain() override {
    start_lock_.Acquire();
    start_lock_.Release();

    for (int i = 0; i < iterations_; i++) {
      lock_.Acquire();
      ++counter_;
      lock_.Release();
    }
  }

 private:
  Lock& start_lock_;
  Lock& lock_;
  const int iterations_;
  int& counter_;
};

}  // namespace

TEST(PartitionAllocLockTest, Contended) {
  int counter = 0;  // *Not* atomic.
  std::vector<internal::base::PlatformThreadHandle> thread_handles;
  constexpr int iterations_per_thread = 1000000;
  constexpr int num_threads = 4;

  Lock lock;
  Lock start_lock;

  ThreadDelegateForContended delegate(start_lock, lock, iterations_per_thread,
                                      counter);

  start_lock.Acquire();  // Make sure that the threads compete, by waiting until
                         // all of them have at least been created.
  for (int i = 0; i < num_threads; ++i) {
    base::PlatformThreadHandle handle;
    base::PlatformThreadForTesting::Create(0, &delegate, &handle);
    thread_handles.push_back(handle);
  }

  start_lock.Release();

  for (int i = 0; i < num_threads; ++i) {
    base::PlatformThreadForTesting::Join(thread_handles[i]);
  }
  EXPECT_EQ(iterations_per_thread * num_threads, counter);
}

namespace {

class ThreadDelegateForSlowThreads
    : public base::PlatformThreadForTesting::Delegate {
 public:
  explicit ThreadDelegateForSlowThreads(Lock& start_lock,
                                        Lock& lock,
                                        int iterations,
                                        int& counter)
      : start_lock_(start_lock),
        lock_(lock),
        iterations_(iterations),
        counter_(counter) {}

  void ThreadMain() override {
    start_lock_.Acquire();
    start_lock_.Release();

    for (int i = 0; i < iterations_; i++) {
      lock_.Acquire();
      ++counter_;
      // Hold the lock for a while, to force futex()-based locks to sleep.
      base::PlatformThread::Sleep(base::Milliseconds(1));
      lock_.Release();
    }
  }

 private:
  Lock& start_lock_;
  Lock& lock_;
  const int iterations_;
  int& counter_;
};

}  // namespace

TEST(PartitionAllocLockTest, SlowThreads) {
  int counter = 0;  // *Not* atomic.
  std::vector<base::PlatformThreadHandle> thread_handles;
  constexpr int iterations_per_thread = 100;
  constexpr int num_threads = 4;

  Lock lock;
  Lock start_lock;

  ThreadDelegateForSlowThreads delegate(start_lock, lock, iterations_per_thread,
                                        counter);

  start_lock.Acquire();  // Make sure that the threads compete, by waiting until
                         // all of them have at least been created.
  for (int i = 0; i < num_threads; i++) {
    base::PlatformThreadHandle handle;
    base::PlatformThreadForTesting::Create(0, &delegate, &handle);
    thread_handles.push_back(handle);
  }

  start_lock.Release();

  for (int i = 0; i < num_threads; i++) {
    base::PlatformThreadForTesting::Join(thread_handles[i]);
  }
  EXPECT_EQ(iterations_per_thread * num_threads, counter);
}

TEST(PartitionAllocLockTest, AssertAcquired) {
  Lock lock;
  lock.Acquire();
  lock.AssertAcquired();
  lock.Release();
}

#if defined(GTEST_HAS_DEATH_TEST) && \
    (PA_BUILDFLAG(DCHECKS_ARE_ON) || \
     PA_BUILDFLAG(ENABLE_PARTITION_LOCK_REENTRANCY_CHECK))

// Need to bypass `-Wthread-safety-analysis` for this test.
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wthread-safety-analysis"
#endif

TEST(PartitionAllocLockTest, ReentrancyDeathTest) {
  Lock lock;
  lock.Acquire();
  EXPECT_DEATH(lock.Acquire(), "");
}

#if __clang__
#pragma clang diagnostic pop
#endif

#endif  // defined(GTEST_HAS_DEATH_TEST) && (PA_BUILDFLAG(DCHECKS_ARE_ON) ||
        // PA_BUILDFLAG(ENABLE_PARTITION_LOCK_REENTRANCY_CHECK))

// AssertAcquired() is only enforced with DCHECK()s.
// DCHECKs don't work with EXPECT_DEATH on official builds.
#if defined(GTEST_HAS_DEATH_TEST) && PA_BUILDFLAG(DCHECKS_ARE_ON) && \
    (!defined(OFFICIAL_BUILD) || PA_BUILDFLAG(IS_DEBUG))

TEST(PartitionAllocLockTest, AssertAcquiredDeathTest) {
  Lock lock;
  EXPECT_DEATH(lock.AssertAcquired(), "");
}

namespace {

class ThreadDelegateForAssertAcquiredAnotherThreadHoldsTheLock
    : public base::PlatformThreadForTesting::Delegate {
 public:
  explicit ThreadDelegateForAssertAcquiredAnotherThreadHoldsTheLock(Lock& lock)
      : lock_(lock) {}

  void ThreadMain() PA_NO_THREAD_SAFETY_ANALYSIS override { lock_.Acquire(); }

 private:
  Lock& lock_;
};

}  // namespace

TEST(PartitionAllocLockTest, AssertAcquiredAnotherThreadHoldsTheLock) {
  Lock lock;
  // PA_NO_THREAD_SAFETY_ANALYSIS: The checker rightfully points out that the
  // lock is still held at the end of the function, which is what we want here.
  ThreadDelegateForAssertAcquiredAnotherThreadHoldsTheLock delegate(lock);
  base::PlatformThreadHandle handle;
  base::PlatformThreadForTesting::Create(0, &delegate, &handle);
  // Join before the test, otherwise some platforms' gtest have trouble with
  // EXPECT_DEATH() and multiple live threads.
  base::PlatformThreadForTesting::Join(handle);

  // DCHECKs don't work with EXPECT_DEATH on official builds.
#if PA_BUILDFLAG(DCHECKS_ARE_ON) && \
    (!defined(OFFICIAL_BUILD) || PA_BUILDFLAG(IS_DEBUG))
  EXPECT_DEATH(lock.AssertAcquired(), "");
#endif
}

#if PA_BUILDFLAG(IS_APPLE)

namespace {

class ThreadDelegateForReinitInOtherThread
    : public base::PlatformThreadForTesting::Delegate {
 public:
  explicit ThreadDelegateForReinitInOtherThread(Lock& lock) : lock_(lock) {}

  void ThreadMain() PA_NO_THREAD_SAFETY_ANALYSIS override {
    lock_.Reinit();
    lock_.Acquire();
    lock_.Release();
  }

 private:
  Lock& lock_;
};

}  // namespace

// On Apple OSes, it is not allowed to unlock a lock from another thread, so
// we need to re-initialize it.
TEST(PartitionAllocLockTest, ReinitInOtherThread) PA_NO_THREAD_SAFETY_ANALYSIS {
  Lock lock;
  lock.Acquire();

  ThreadDelegateForReinitInOtherThread delegate(lock);
  base::PlatformThreadHandle handle;
  base::PlatformThreadForTesting::Create(0, &delegate, &handle);
  base::PlatformThreadForTesting::Join(handle);
}
#endif  // PA_BUILDFLAG(IS_APPLE)

#endif  // defined(GTEST_HAS_DEATH_TEST) && PA_BUILDFLAG(DCHECKS_ARE_ON)

// Priority Inheritance Tests --------------------------------------------------

#if PA_BUILDFLAG(ENABLE_PARTITION_LOCK_PRIORITY_INHERITANCE)
namespace {

// CPU bound work for the threads to eat up CPU cycles.
void BusyLoop(size_t n) {
  auto generator = base::InsecureRandomGenerator::ConstructForTesting();
  int sum = 0;
  for (size_t i = 0; i < n; i++) {
    if (i == 0 || (generator.RandUint32() & 1)) {
      sum++;
    }
  }

  ASSERT_GT(sum, 0) << "The busy loop was optimized out.";
}

class TestThreadBase : public base::PlatformThreadForTesting::Delegate {
 public:
  TestThreadBase() = default;
  explicit TestThreadBase(int nice_value) : nice_value_(nice_value) {}

  void Create() {
    ASSERT_TRUE(base::PlatformThreadForTesting::Create(0, this, &handle_));
  }

  void Join() { base::PlatformThreadForTesting::Join(handle_); }

  void ThreadMain() override {
    if (nice_value_) {
      ASSERT_EQ(setpriority(PRIO_PROCESS, 0, nice_value_), 0);
    }

    Body();
  }

  virtual void Body() = 0;

 private:
  int nice_value_;
  base::PlatformThreadHandle handle_;
};

class PriorityInheritanceTest {
 public:
  // The average value of MeasureRunTime() over |num_samples| iterations.
  static base::TimeDelta MeasureAverageRunTime(int num_samples = 10) {
    base::TimeDelta total_run_time;
    for (int i = 0; i < num_samples; i++) {
      total_run_time += MeasureRunTime();
    }

    return total_run_time / num_samples;
  }

  // Measure the time taken for a low-priority thread (nice value = 2) to
  // perform CPU bound work when it holds a lock that is awaited by a
  // high-priority thread (nice value = -16).
  static base::TimeDelta MeasureRunTime() {
    Lock lock;

    {
      // Migrate the lock to use the PI futex, if enabled, by acquiring and
      // releasing the lock.
      ScopedGuard guard(lock);
    }

    HighPriorityThread thread_a(&lock);
    LowPriorityThread thread_b(&lock, &thread_a);

    // Create the low-priority thread which is responsible for creating the
    // high-priority thread. Wait for both threads to finish before recording
    // the elapsed time.
    thread_b.Create();
    thread_b.Join();
    thread_a.Join();

    return thread_b.GetTestRunTime();
  }

  // Class that spawns a thread on object creation to perform CPU bound work
  // until the object is destroyed.
  class CPUBoundWorkerThread : public TestThreadBase {
   public:
    explicit CPUBoundWorkerThread() : should_shutdown_(false) { Create(); }

    ~CPUBoundWorkerThread() override {
      should_shutdown_.store(true, std::memory_order_relaxed);
      Join();
    }

    void Body() override {
      while (!should_shutdown_.load(std::memory_order_relaxed)) {
        BusyLoop(10);
      }
    }

   private:
    std::atomic<bool> should_shutdown_;
  };

  class HighPriorityThread : public TestThreadBase {
   public:
    explicit HighPriorityThread(Lock* lock)
        : TestThreadBase(-16), lock_(lock) {}

    void Body() override {
      // Wait on the lock to be released once the low-priority thread is
      // done. In the case when priority inheritance mutexes are enabled,
      // this should boost the priority of the low-priority thread to the
      // priority of the highest priority waiter (i.e. the current thread).
      ScopedGuard guard(*lock_);
      BusyLoop(10);
    }

   private:
    Lock* lock_;
  };

  class LowPriorityThread : public TestThreadBase {
   public:
    LowPriorityThread(Lock* lock, HighPriorityThread* thread_a)
        : TestThreadBase(2), lock_(lock), thread_a_(thread_a) {}

    void Body() override {
      // Acquire the lock before creating the high-priority thread, so that
      // the higher priority thread is blocked on the current thread while
      // the current thread performs CPU-bound work.
      ScopedGuard guard(*lock_);
      thread_a_->Create();

      // Before performing the CPU bound work, wait for the thread A to
      // begin waiting on the lock.
      //
      // NOTE: Unlike //base/synchronization/lock_unittest.cc where we use an
      // atomic to signal the low-priority thread to start its work, we instead
      // inspect the lock itself to know if there is another thread waiting on
      // the lock. This is necessary because a thread trying to acquire a
      // partition allocator lock spins in userspace yielding the CPU multiple
      // times between attempts to to acquire the lock before waiting in the
      // kernel and this process can take longer than the low-priority thread's
      // workload itself on x64 emulators. By waiting for the high-priority
      // thread to enter the kernel and potentially boost the low-priority
      // thread, the test beomes more consistent and reliable.
      while (!lock_->HasWaitersForTesting()) {
        (void)0;
      }

      base::TimeTicks start_time = base::TimeTicks::Now();
      BusyLoop(2000000);
      test_run_time_ = base::TimeTicks::Now() - start_time;
    }

    base::TimeDelta GetTestRunTime() const { return test_run_time_; }

   private:
    Lock* lock_;
    HighPriorityThread* thread_a_;
    base::TimeDelta test_run_time_;
  };
};

static int32_t major_version, minor_version, bugfix_version;

bool PriorityInheritanceLocksSupported() {
  struct utsname info;

  EXPECT_EQ(uname(&info), 0);
  int num_read = PA_UNSAFE_TODO(sscanf(info.release, "%d.%d.%d", &major_version,
                                       &minor_version, &bugfix_version));
  if (num_read < 1) {
    major_version = 0;
  }
  if (num_read < 2) {
    minor_version = 0;
  }
  if (num_read < 3) {
    bugfix_version = 0;
  }

  // Same as ::base::KernelSupportsPriorityInheritanceFutex()
  return (major_version > 6) ||
         ((major_version == 6) &&
          ((minor_version > 12) ||
           (minor_version == 12 && bugfix_version > 13) ||
           (minor_version == 6 && bugfix_version > 29) ||
           (minor_version == 1 && bugfix_version > 75)));
}
}  // namespace

// Tests that the time taken by a higher-priority thread to acquire a lock held
// by a lower-priority thread is indeed reduced by priority inheritance.
//
// NOTE: This test is a reimplementation of LockTest.PriorityIsInherited from
// //base/synchronization/lock_unittest.cc for partition alloc with a few key
// differences to prevent the CQ bots from timing out during the test:
//   a) The low-priority thread is set to nice value 2, since it might not make
//      progress quick enough in the non-PI case.
//   b) The CPU-bound worker threads are created only once instead of
//      per-iteration to minimize the number of threads created. This is because
//      test run time on emulators is significantly affected by the time taken
//      to join all the threads.
TEST(PartitionAllocLockTest, PriorityIsInherited) {
  base::TimeDelta avg_test_run_time_with_pi, avg_test_run_time_without_pi;

  // Priority inheritance locks are not supported on Android kernels < 6.1
  if (!PriorityInheritanceLocksSupported()) {
    GTEST_SKIP()
        << "internal::Lock does not handle multiple thread priorities.";
  }

  // Keep all the cores busy with a workload of CPU bound thread to reduce
  // flakiness in the test by skewing the CPU time between the high-priority
  // and low-priority measurement threads.
  std::vector<PriorityInheritanceTest::CPUBoundWorkerThread>
      cpu_bound_worker_threads(15);

  // Since we only support migration of the lock from a non-PI lock to a PI
  // lock, the the test without PI locks needs to be run first This is because
  // unlike base::Lock, where the PI-ness of the lock is stored in the lock
  // object at lock creation time and the global state is not queried later, for
  // partition_alloc::internal::Lock the PI-ness of the lock is set at the first
  // unlock after the feature is enabled but the global feature state is still
  // queried at every unlock.
  {
    avg_test_run_time_without_pi =
        PriorityInheritanceTest::MeasureAverageRunTime();
  }

  {
    SpinningMutex::EnableUsePriorityInheritance();
    avg_test_run_time_with_pi =
        PriorityInheritanceTest::MeasureAverageRunTime();
  }

  // During the time in which the thread A is waiting on the lock to be released
  // by the thread B, the thread B runs at priority 130 in the non-PI
  // case and at priority 104 in the PI case.
  //
  // Based on the Linux kernel's allocation of CPU shares documented in
  // https://elixir.bootlin.com/linux/v6.12.5/source/kernel/sched/core.c#L9998,
  // a thread running at priority 104 (nice value = -16) gets 36291 shares of
  // the CPU, a thread at priority 120 (nice value = 0) gets 1024 shares and a
  // thread at priority 122 (nice value = 2) gets 655 shares of the CPU.
  //
  // Assuming no other threads except the ones created by this test are running,
  // during the time in which thread A is waiting on the lock to be released by
  // thread B, thread B gets 655/(15*1024 + 655) ≈ 4.1% of the CPU time in the
  // non-PI case and 36291/(36291 + 15*1024) ≈ 70% of the CPU time in the PI
  // case. This is approximately a 17x difference in CPU shares allocated to
  // the thread B when it is doing CPU-bound work.
  //
  // The test is thus designed such that the measured run time is thread B's CPU
  // bound work. While there are other factors at play that determine the
  // measured run time such as the frequency at which the CPU is running, we can
  // expect that there will be at least an order of magnitude of disparity in
  // the test run times with and without PI.
  //
  // In order to reduce test flakiness while still eliminating the possibility
  // of variance in measurements accounting for the test results, we
  // conservatively expect a 3x improvement.
  EXPECT_GT(avg_test_run_time_without_pi, 3 * avg_test_run_time_with_pi)
      << " on kernel version " << major_version << "." << minor_version << "."
      << bugfix_version;
}
namespace {
constexpr int kNumThreads = 64, kNumAcquiresPerThread = 16;
class FutexMigrationTestThread : public TestThreadBase {
 public:
  FutexMigrationTestThread(Lock* lock,
                           std::atomic<int>* num_threads_started,
                           int thread_id)
      : TestThreadBase(0),
        lock_(lock),
        num_threads_started_(num_threads_started),
        thread_id_(thread_id) {}

  void Body() override {
    num_threads_started_->fetch_add(1, std::memory_order_relaxed);
    for (int j = 0; j < kNumAcquiresPerThread; j++) {
      // Only one of the many threads we spawn triggers the migration of the
      // lock futex. We want to make it likely that there are threads waiting
      // on the non-PI futex in the kernel when the migration happens to try
      // and get better code coverage for futex migration. So we try and make
      // the migration happen somewhere in the middle of the test.
      if (j == kNumAcquiresPerThread / 4 && thread_id_ == kNumThreads / 4) {
        SpinningMutex::EnableUsePriorityInheritance();
      }

      ScopedGuard guard(*lock_);
      BusyLoop(100000);
    }
  }

 private:
  Lock* lock_;
  std::atomic<int>* num_threads_started_;
  int thread_id_;
};

// FUTEX_LOCK_PI2 was introduced in kernel 5.14.
bool FutexLockPI2Supported() {
  struct utsname info;
  if (uname(&info) != 0) {
    return false;
  }
  int32_t major = 0, minor = 0;
  PA_UNSAFE_TODO(sscanf(info.release, "%d.%d", &major, &minor));
  return major > 5 || (major == 5 && minor >= 14);
}

}  // namespace

// The |PartitionAllocUsePriorityInheritanceLocks| feature is enabled after the
// PartitionRoot lock is initialized. This requires the lock to internally
// migrate all its waiters from the non-PI futex to the PI futex. So we test if
// the migration works correctly by enabling the feature with multiple threads
// contending for the same lock.
TEST(PartitionAllocLockTest, FutexMigration) {
  if (!FutexLockPI2Supported()) {
    GTEST_SKIP() << "FUTEX_LOCK_PI2 requires kernel >= 5.14";
  }

  Lock lock;
  std::atomic<int> num_threads_started{0};

  std::vector<FutexMigrationTestThread> threads;
  for (int i = 0; i < kNumThreads; i++) {
    threads.emplace_back(&lock, &num_threads_started, i);
  }

  {
    ScopedGuard guard(lock);
    // Create the threads after acquiring the lock and release it only after all
    // threads have started to ensure heavy and consistent contention on the
    // lock.
    std::ranges::for_each(threads, &FutexMigrationTestThread::Create);
    while (num_threads_started.load(std::memory_order_relaxed) < kNumThreads) {
      usleep(10);
    }
  }

  // The test is complete if all the threads manage to acquire the lock
  // post-migration.
  std::ranges::for_each(threads, &FutexMigrationTestThread::Join);
}

#endif  // PA_BUILDFLAG(ENABLE_PARTITION_LOCK_PRIORITY_INHERITANCE)

namespace {

class SimpleBarrier {
 public:
  explicit SimpleBarrier(size_t n) : num_threads_(n) {}

  void Synchronize() {
    num_threads_.fetch_sub(1, std::memory_order_release);
    while (num_threads_.load(std::memory_order_acquire)) {
      base::PlatformThread::Sleep(base::Microseconds(10));
    }
  }

 private:
  std::atomic<size_t> num_threads_;
};

class MetricsRecorderTestThread
    : public base::PlatformThreadForTesting::Delegate {
 public:
  MetricsRecorderTestThread(Lock& lock, SimpleBarrier& sync_point)
      : lock_(lock), sync_point_(sync_point) {}

  void ThreadMain() override {
    // Signal that this thread has taken the lock, then go to sleep for a
    // long-time holding the lock to make sure the other thread takes the slow
    // path of acquire and will record a sample
    ScopedGuard guard(lock_);
    sync_point_.Synchronize();
    base::PlatformThread::Sleep(base::Seconds(1));
  }

 private:
  Lock& lock_;
  SimpleBarrier& sync_point_;
};

// Two threads try to acquire the lock with very high-probability of lock
// contention.
void MakeThreadsContendOnLock() {
  Lock lock;
  base::PlatformThreadHandle handle;
  SimpleBarrier sync_point{2};
  MetricsRecorderTestThread thread(lock, sync_point);

  // Create another thread and wait for it to acquire the lock before trying to
  // acquire the lock to create contention.
  base::PlatformThreadForTesting::Create(0, &thread, &handle);
  sync_point.Synchronize();
  {
    ScopedGuard guard(lock);
  }

  base::PlatformThreadForTesting::Join(handle);
}

// Checks if the number of samples recorded until the object goes out of scope
// (with sampling ratio set to 1) is equal to the expected value.
class LockMetricsTestHelper : public LockMetricsRecorderInterface {
 public:
  LockMetricsTestHelper() {
    SpinningMutex::SetLockMetricsRecorderForTesting(this);
  }

  ~LockMetricsTestHelper() override {
    SpinningMutex::SetLockMetricsRecorderForTesting(nullptr);
  }

  bool ShouldRecordLockAcquisitionTime() const override { return true; }

  void RecordLockAcquisitionTime(base::TimeDelta sample) override {
    num_samples_recorded_++;
  }

  size_t GetRecordedSamplesCount() const { return num_samples_recorded_; }

 private:
  size_t num_samples_recorded_ = 0;
};

}  // namespace

// Test that no samples are recorded when there is no contention on the lock.
TEST(PartitionAllocLockTest, MetricsNotRecordedWhenUncontended) {
  LockMetricsTestHelper helper;

  {
    Lock lock;
    ScopedGuard guard(lock);
  }

  size_t num_samples_recorded = helper.GetRecordedSamplesCount();
  EXPECT_EQ(num_samples_recorded, 0U);
}

// Test that samples are recorded when there is contention on the lock.
TEST(PartitionAllocLockTest, MetricsRecordedWhenContended) {
  LockMetricsTestHelper helper;

  MakeThreadsContendOnLock();

  size_t num_samples_recorded = helper.GetRecordedSamplesCount();
  EXPECT_GE(num_samples_recorded, 1U);
}

}  // namespace partition_alloc::internal