View Javadoc
1   /*
2    * Copyright 2025 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.util.concurrent;
17  
18  import java.util.Collections;
19  import java.util.IdentityHashMap;
20  import java.util.Set;
21  import java.util.concurrent.TimeUnit;
22  import java.util.concurrent.atomic.AtomicLong;
23  import java.util.concurrent.locks.Condition;
24  import java.util.concurrent.locks.ReentrantLock;
25  
26  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
27  import static java.util.Objects.requireNonNull;
28  
29  /**
30   * The default {@link MockTicker} implementation.
31   */
32  final class DefaultMockTicker implements MockTicker {
33  
34      // The lock is fair, so waiters get to process condition signals in the order they (the waiters) queued up.
35      private final ReentrantLock lock = new ReentrantLock(true);
36      private final Condition tickCondition = lock.newCondition();
37      private final Condition sleeperCondition = lock.newCondition();
38      private final AtomicLong nanoTime = new AtomicLong();
39      private final Set<Thread> sleepers = Collections.newSetFromMap(new IdentityHashMap<>());
40  
41      @Override
42      public long nanoTime() {
43          return nanoTime.get();
44      }
45  
46      @Override
47      public void sleep(long delay, TimeUnit unit) throws InterruptedException {
48          checkPositiveOrZero(delay, "delay");
49          requireNonNull(unit, "unit");
50  
51          if (delay == 0) {
52              return;
53          }
54  
55          final long delayNanos = unit.toNanos(delay);
56          lock.lockInterruptibly();
57          try {
58              final long startTimeNanos = nanoTime();
59              sleepers.add(Thread.currentThread());
60              sleeperCondition.signalAll();
61              do {
62                  tickCondition.await();
63              } while (nanoTime() - startTimeNanos < delayNanos);
64          } finally {
65              sleepers.remove(Thread.currentThread());
66              lock.unlock();
67          }
68      }
69  
70      /**
71       * Wait for the given thread to enter the {@link #sleep(long, TimeUnit)} method, and block.
72       */
73      public void awaitSleepingThread(Thread thread) throws InterruptedException {
74          lock.lockInterruptibly();
75          try {
76              while (!sleepers.contains(thread)) {
77                  sleeperCondition.await();
78              }
79          } finally {
80              lock.unlock();
81          }
82      }
83  
84      @Override
85      public void advance(long amount, TimeUnit unit) {
86          checkPositiveOrZero(amount, "amount");
87          requireNonNull(unit, "unit");
88  
89          if (amount == 0) {
90              return;
91          }
92  
93          final long amountNanos = unit.toNanos(amount);
94          lock.lock();
95          try {
96              nanoTime.addAndGet(amountNanos);
97              tickCondition.signalAll();
98          } finally {
99              lock.unlock();
100         }
101     }
102 }
103