1 /*
2 * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @library /test/lib
27 * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.value
28 * @enablePreview
29 * @run main/othervm/timeout=2000 ValueTearingTest
30 */
31
32
33 import java.time.Instant;
34 import java.util.ArrayList;
35 import java.util.concurrent.atomic.AtomicLong;
36 import java.util.concurrent.BrokenBarrierException;
37 import java.util.concurrent.CyclicBarrier;
38 import jdk.internal.value.ValueClass;
39 import jdk.test.lib.Asserts;
40
41 public class ValueTearingTest {
42
43 static final int N_SCENARIO = 5;
44 static final int N_PRECOMPUTED = 100;
45 static final int[] INCREMENTS = {1, 2, 3, 5, 7, 11, 13, 17, 23, 29, 31, 37, 41, 43,
46 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
47 107, 109, 113, 127, 131};
48
49 static int N_WRITERS;
50 static int N_READERS;
51 static final int DURATION_MILLISECONDS = 1 * 60 * 1000;
52 static final Object[] objectArray = new Object[1];
53 static AtomicLong[] scenarioFailures = new AtomicLong[N_SCENARIO];
54
55 static CyclicBarrier barrier;
56
57 static {
58 int nCpu = Runtime.getRuntime().availableProcessors();
59 N_WRITERS = Math.min((nCpu / 2), INCREMENTS.length);
60 N_READERS = nCpu - N_WRITERS;
61 Asserts.assertTrue(N_WRITERS > 0, "At least one writer thread is required");
62 Asserts.assertTrue(N_READERS > 0, "At least one reader thread is required");
63 barrier = new CyclicBarrier(nCpu + 1);
64 objectArray[0] = new Value(1);
65 for (int i = 0; i < N_SCENARIO; i++) {
66 scenarioFailures[i] = new AtomicLong();
67 }
68 }
69
70 static value class Value {
71 int i;
72 byte b;
73
74 static Value[] precomputedValues = new Value[100];
75 static Value sharedInstance = new Value(0);
76
77
78 static {
79 for (int i = 0; i < N_PRECOMPUTED; i++) {
80 precomputedValues[i] = new Value(i);
81 }
82 }
83
84 Value(int i) {
85 this.i = i;
86 b = (byte)(i/3);
87 }
88
89 public boolean existInPredefinedValueSet() {
90 for (Value precomputedValue : precomputedValues) {
91 if (this == precomputedValue) return true;
92 }
93 return false;
94 }
95 }
96
97 static class Container {
98 static Container sharedInstance = new Container();
99 static Container[] precomputedContainers = new Container[N_PRECOMPUTED];
100 static Value[] sharedArray;
101
102 static {
103 for (int i = 0; i < N_PRECOMPUTED; i++) {
104 precomputedContainers[i] = new Container(i);
105 }
106 sharedArray = new Value[0];
107 Asserts.assertTrue(ValueClass.isFlatArray(sharedArray));
108 }
109
110 Value val;
111
112 Container() {
113 this(0);
114 }
115
116 Container(int i) {
117 val = new Value(i);
118 }
119 }
120
121 static class Scenario {
122 static final int BATCH_SIZE = 10_000;
123
124 static int incrementIndex(int idx, int n) {
125 idx += INCREMENTS[n];
126 if (idx >= N_PRECOMPUTED) idx -= N_PRECOMPUTED;
127 return idx;
128 }
129
130 static void scenario0_writer(int n) {
131 // Flat array -> buffered -> reference array [System.arraycopy]
132 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
133 int idx = 0;
134 while (Instant.now().isBefore(end)) {
135 for (int i = 0; i < BATCH_SIZE; i++) {
136 System.arraycopy(Value.precomputedValues, idx, objectArray, 0, 1);
137 idx = incrementIndex(idx, n);
138 }
139 }
140 }
141
142 static void scenario0_reader() {
143 // flat array -> buffered -> reference array [System.arraycopy]
144 final var end0 = Instant.now().plusMillis(DURATION_MILLISECONDS);
145 int failures = 0;
146 while (Instant.now().isBefore(end0)) {
147 Value v = (Value)objectArray[0];
148 if (!v.existInPredefinedValueSet()) {
149 failures++;
150 }
151 }
152 if (failures > 0) {
153 scenarioFailures[0].getAndAdd(failures);
154 }
155 }
156
157 static void scenario1_writer(int n) {
158 // Flat array -> buffered -> reference array [Not System.arraycopy]
159 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
160 int idx = 0;
161 while (Instant.now().isBefore(end)) {
162 for (int i = 0; i < BATCH_SIZE; i++) {
163 objectArray[0] = Value.precomputedValues[idx];
164 idx = incrementIndex(idx, n);
165 }
166 }
167 }
168
169 static void scenario1_reader() {
170 // flat array -> buffered -> reference array [Not System.arraycopy]
171 final var end0 = Instant.now().plusMillis(DURATION_MILLISECONDS);
172 int failures = 0;
173 while (Instant.now().isBefore(end0)) {
174 Value v = (Value)objectArray[0];
175 if (!v.existInPredefinedValueSet()) {
176 failures++;
177 }
178 }
179 if (failures > 0) {
180 scenarioFailures[1].getAndAdd(failures);
181 }
182 }
183
184 static void scenario2_writer(int n) {
185 // Flat field -> buffered -> reference array
186 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
187 int idx = 0;
188 while (Instant.now().isBefore(end)) {
189 for (int i = 0; i < BATCH_SIZE; i++) {
190 objectArray[0] = Container.precomputedContainers[idx].val;
191 idx = incrementIndex(idx, n);
192 }
193 }
194 }
195
196 static void scenario2_reader() {
197 // Flat field -> buffered -> reference array
198 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
199 int failures = 0;
200 while (Instant.now().isBefore(end)) {
201 Value v = (Value)objectArray[0];
202 if (!v.existInPredefinedValueSet()) {
203 failures++;
204 }
205 }
206 if (failures > 0) {
207 scenarioFailures[2].getAndAdd(failures);
208 }
209 }
210
211 static void scenario3_writer(int n) {
212 // Flat field -> buffered -> reference field
213 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
214 int idx = 0;
215 while (Instant.now().isBefore(end)) {
216 for (int i = 0; i < BATCH_SIZE; i++) {
217 Value.sharedInstance = Container.precomputedContainers[idx].val;
218 idx = incrementIndex(idx, n);
219 }
220 }
221 }
222
223 static void scenario3_reader() {
224 // Flat field -> buffered -> reference field
225 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
226 int failures = 0;
227 while (Instant.now().isBefore(end)) {
228 Value v = Value.sharedInstance;
229 if (!v.existInPredefinedValueSet()) {
230 failures++;
231 }
232 }
233 if (failures > 0) {
234 scenarioFailures[3].getAndAdd(failures);
235 }
236 }
237
238 static void scenario4_writer(int n) {
239 // Flat array -> buffered -> reference field
240 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
241 int idx = 0;
242 while (Instant.now().isBefore(end)) {
243 for (int i = 0; i < BATCH_SIZE; i++) {
244 Value.sharedInstance = Value.precomputedValues[idx];
245 idx = incrementIndex(idx, n);
246 }
247 }
248 }
249
250 static void scenario4_reader() {
251 // Flat array -> buffered -> reference field
252 final var end = Instant.now().plusMillis(DURATION_MILLISECONDS);
253 int failures = 0;
254 while (Instant.now().isBefore(end)) {
255 Value v = Value.sharedInstance;
256 if (!v.existInPredefinedValueSet()) {
257 failures++;
258 }
259 }
260 if (failures > 0) {
261 scenarioFailures[4].getAndAdd(failures);
262 }
263 }
264 }
265
266 static class Writer implements Runnable {
267 final int id;
268
269 Writer(int i) {
270 id = i;
271 }
272
273 @Override
274 public void run() {
275 try {
276 // Reflection could be used to invoke methods, but keep it simple for now
277 barrier.await();
278 Scenario.scenario0_writer(id);
279 barrier.await();
280 Scenario.scenario1_writer(id);
281 barrier.await();
282 Scenario.scenario2_writer(id);
283 barrier.await();
284 Scenario.scenario3_writer(id);
285 barrier.await();
286 Scenario.scenario4_writer(id);
287 barrier.await();
288 } catch (InterruptedException e) {
289 throw new RuntimeException(e);
290 } catch (BrokenBarrierException e) {
291 throw new RuntimeException(e);
292 }
293 }
294 }
295
296 static class Reader implements Runnable {
297
298 @Override
299 public void run() {
300 int scenario = 0;
301 try {
302 barrier.await();
303 Scenario.scenario0_reader();
304 barrier.await();
305 Scenario.scenario1_reader();
306 barrier.await();
307 Scenario.scenario2_reader();
308 barrier.await();
309 Scenario.scenario3_reader();
310 barrier.await();
311 Scenario.scenario4_reader();
312 barrier.await();
313 } catch (InterruptedException e) {
314 throw new RuntimeException(e);
315 } catch (BrokenBarrierException e) {
316 throw new RuntimeException(e);
317 }
318 }
319 }
320
321 public static void main(String[] args) {
322 System.out.println("N_WRITERS = " + N_WRITERS + " N_READERS = " + N_READERS);
323 Thread[] writers = new Thread[N_WRITERS];
324 Thread[] readers = new Thread[N_READERS];
325
326 for (int i = 0; i < N_WRITERS; i++) {
327 writers[i] = new Thread(new Writer(i), "Writer-" + i);
328 writers[i].start();
329 }
330 for (int i = 0; i < N_READERS; i++) {
331 readers[i] = new Thread(new Reader(), "Reader-" + i);
332 readers[i].start();
333 }
334
335 try {
336 System.out.println("Waiting");
337 try {
338 Thread.sleep(3000);
339 } catch (InterruptedException e) { }
340 barrier.await();
341 boolean failed = false;
342 for (int i = 0; i < N_SCENARIO; i++) {
343 System.out.println("Running scenario " + i);
344 barrier.await();
345 if (scenarioFailures[i].get() != 0) {
346 failed = true;
347 System.out.println("Scenario " + i + " failures: " + scenarioFailures[i].get());
348 }
349 }
350 if (failed) {
351 throw new RuntimeException("Value tearing detected");
352 } else {
353 System.out.println("Done");
354 }
355 } catch (InterruptedException e) {
356 throw new RuntimeException(e);
357 } catch (BrokenBarrierException e) {
358 throw new RuntimeException(e);
359 }
360 }
361 }