xref: /aosp_15_r20/art/test/2029-contended-monitors/src/Main.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 import java.util.concurrent.atomic.AtomicInteger;
17 
18 public class Main {
19 
20   private final boolean PRINT_TIMES = false;  // False for use as run test.
21 
22   // Number of increments done by each thread.  Must be multiple of largest hold time below,
23   // times any possible thread count. We reduce this below if PRINT_TIMES is not set, and
24   // even more in debuggable mode.
25   private final int UNSCALED_TOTAL_ITERS  = 16_000_000;
26   private final int UNSCALED_MAX_HOLD_TIME = 2_000_000;
27   private int totalIters;
28   private int maxHoldTime;
29 
30   private int counter;
31 
32   private AtomicInteger atomicCounter = new AtomicInteger();
33 
34   private Object lock;
35 
36   private int currentThreadCount = 0;
37 
38   private static boolean debuggable = false;  // Running in a debuggable ART environment.
39 
40   // A function such that if we repeatedly apply it to -1, the value oscillates
41   // between -1 and 3. Thus the average value is 1.
42   // This is designed to make it hard for the compiler to predict the values in
43   // the sequence.
nextInt(int x)44   private int nextInt(int x) {
45     if (x < 0) {
46       return x * x + 2;
47     } else {
48       return x - 4;
49     }
50   }
51 
52   // Increment counter by n, holding lock for time roughly propertional to n.
53   // N must be even.
holdFor(Object lock, int n)54   private void holdFor(Object lock, int n) {
55     synchronized(lock) {
56       int y = -1;
57       for (int i = 0; i < n; ++i) {
58         counter += y;
59         y = nextInt(y);
60       }
61     }
62   }
63 
64   // Increment local by an even number n in a way that takes time roughly proportional
65   // to n.
spinFor(int n)66   private void spinFor(int n) {
67     int y = -1;
68     int local_counter = 0;
69     for (int i = 0; i < n; ++i) {
70       local_counter += y;
71       y = nextInt(y);
72     }
73     if (local_counter != n) {
74       throw new Error();
75     }
76   }
77 
78   private class RepeatedLockHolder implements Runnable {
RepeatedLockHolder(boolean shared, int n )79     RepeatedLockHolder(boolean shared, int n /* even */) {
80       sharedLock = shared;
81       holdTime = n;
82     }
83     @Override
run()84     public void run() {
85       Object myLock = sharedLock ? lock : new Object();
86       int nIters = totalIters / currentThreadCount / holdTime;
87       for (int i = 0; i < nIters; ++i) {
88         holdFor(myLock, holdTime);
89       }
90     }
91     private boolean sharedLock;
92     private int holdTime;
93   }
94 
95   private class RepeatedIntermittentLockHolder implements Runnable {
RepeatedIntermittentLockHolder(boolean shared, int n )96     RepeatedIntermittentLockHolder(boolean shared, int n /* even */) {
97       sharedLock = shared;
98       holdTime = n;
99     }
100     @Override
run()101     public void run() {
102       Object myLock = sharedLock ? lock : new Object();
103       int iter_divisor = 10 * currentThreadCount * holdTime;
104       int nIters = totalIters / iter_divisor;
105       if (totalIters % iter_divisor != 0 || holdTime % 2 == 1) {
106         System.err.println("Misconfigured: totalIters = " + totalIters
107             + " iter_divisor = "  + iter_divisor);
108       }
109       for (int i = 0; i < nIters; ++i) {
110         spinFor(9 * holdTime);
111         holdFor(myLock, holdTime);
112       }
113     }
114     private boolean sharedLock;
115     private int holdTime;
116   }
117 
118   private class SleepyLockHolder implements Runnable {
SleepyLockHolder(boolean shared)119     SleepyLockHolder(boolean shared) {
120       sharedLock = shared;
121     }
122     @Override
run()123     public void run() {
124       Object myLock = sharedLock ? lock : new Object();
125       int nIters = totalIters / currentThreadCount / 10_000;
126       for (int i = 0; i < nIters; ++i) {
127         synchronized(myLock) {
128           try {
129             Thread.sleep(2);
130           } catch(InterruptedException e) {
131             throw new AssertionError("Unexpected interrupt");
132           }
133           counter += 10_000;
134         }
135       }
136     }
137     private boolean sharedLock;
138   }
139 
140   // Increment atomicCounter n times, on average by 1 each time.
141   private class RepeatedIncrementer implements Runnable {
142     @Override
run()143     public void run() {
144       int y = -1;
145       int nIters = totalIters / currentThreadCount;
146       for (int i = 0; i < nIters; ++i) {
147         atomicCounter.addAndGet(y);
148         y = nextInt(y);
149       }
150     }
151   }
152 
153   // Run n threads doing work. Return the elapsed time this took, in milliseconds.
runMultiple(int n, Runnable work)154   private long runMultiple(int n, Runnable work) {
155     Thread[] threads = new Thread[n];
156     // Replace lock, so that we start with a clean, uninflated lock each time.
157     lock = new Object();
158     for (int i = 0; i < n; ++i) {
159       threads[i] = new Thread(work);
160     }
161     long startTime = System.currentTimeMillis();
162     for (int i = 0; i < n; ++i) {
163       threads[i].start();
164     }
165     for (int i = 0; i < n; ++i) {
166       try {
167         threads[i].join();
168       } catch(InterruptedException e) {
169         throw new AssertionError("Unexpected interrupt");
170       }
171     }
172     return System.currentTimeMillis() - startTime;
173   }
174 
175   // Run on different numbers of threads.
runAll(Runnable work, Runnable init, Runnable checker)176   private void runAll(Runnable work, Runnable init, Runnable checker) {
177     for (int i = 1; i <= 8; i *= 2) {
178       currentThreadCount = i;
179       init.run();
180       long time = runMultiple(i, work);
181       if (PRINT_TIMES) {
182         System.out.print(time + (i == 8 ? "\n" : "\t"));
183       }
184       checker.run();
185     }
186   }
187 
188   private class CheckAtomicCounter implements Runnable {
189     @Override
run()190     public void run() {
191       if (atomicCounter.get() != totalIters) {
192         throw new AssertionError("Failed atomicCounter postcondition check for "
193             + currentThreadCount + " threads");
194       }
195     }
196   }
197 
198   private class CheckCounter implements Runnable {
199     private final int expected;
CheckCounter(int e)200     public CheckCounter(int e) {
201       expected = e;
202     }
203     @Override
run()204     public void run() {
205       if (counter != expected) {
206         throw new AssertionError("Failed counter postcondition check for "
207             + currentThreadCount + " threads, expected " + expected + " got " + counter);
208       }
209     }
210   }
211 
run()212   private void run() {
213     int scale = PRINT_TIMES ? 1 : (debuggable ? 20 : 10);
214     totalIters = UNSCALED_TOTAL_ITERS / scale;
215     maxHoldTime = UNSCALED_MAX_HOLD_TIME / scale;
216     if (PRINT_TIMES) {
217       System.out.println("All times in milliseconds for 1, 2, 4 and 8 threads");
218     }
219     System.out.println("Atomic increments");
220     runAll(new RepeatedIncrementer(), () -> { atomicCounter.set(0); }, new CheckAtomicCounter());
221     for (int i = 2; i <= UNSCALED_MAX_HOLD_TIME / 100; i *= 10) {
222       // i * 8 (max thread count) divides totalIters
223       System.out.println("Hold time " + i + ", shared lock");
224       runAll(new RepeatedLockHolder(true, i), () -> { counter = 0; },
225           new CheckCounter(totalIters));
226     }
227     for (int i = 2; i <= UNSCALED_MAX_HOLD_TIME / 1000; i *= 10) {
228       // i * 8 (max thread count) divides totalIters
229       System.out.println("Hold time " + i + ", pause time " + (9 * i) + ", shared lock");
230       runAll(new RepeatedIntermittentLockHolder(true, i), () -> { counter = 0; },
231           new CheckCounter(totalIters / 10));
232     }
233     if (PRINT_TIMES) {
234       for (int i = 2; i <= maxHoldTime; i *= 1000) {
235         // i divides totalIters
236         System.out.println("Hold time " + i + ", private lock");
237         // Since there is no mutual exclusion final counter value is unpredictable.
238         runAll(new RepeatedLockHolder(false, i), () -> { counter = 0; }, () -> {});
239       }
240     }
241     System.out.println("Hold for 2 msecs while sleeping, shared lock");
242     runAll(new SleepyLockHolder(true), () -> { counter = 0; }, new CheckCounter(totalIters));
243     System.out.println("Hold for 2 msecs while sleeping, private lock");
244     runAll(new SleepyLockHolder(false), () -> { counter = 0; }, () -> {});
245   }
246 
main(String[] args)247   public static void main(String[] args) {
248     if (System.getProperty("java.vm.name").equalsIgnoreCase("Dalvik")) {
249       try {
250         System.loadLibrary(args[0]);
251         debuggable = isDebuggable();
252       } catch (Throwable t) {
253         // Conservatively assume we might be debuggable, and pretend we loaded the library.
254         // As of June 2024, we seem to get here with atest.
255         debuggable = true;
256         System.out.println("JNI_OnLoad called");
257       }
258     } else {
259       System.out.println("JNI_OnLoad called");  // Not really, but match expected output.
260     }
261     System.out.println("Starting");
262     new Main().run();
263   }
264 
isDebuggable()265   private static native boolean isDebuggable();
266 }
267