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 }