/
githubmirror
/
jdk21u
Обзор
Документация
Войти
/
githubmirror
/
jdk21u
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/lang/StringBuilder/RacingSBThreads.java
303 строки
11 KB
Goetz Lindenmaier
8357380: java/lang/StringBuilder/RacingSBThreads.java times out with C1
13 янв 2026, 09:22
13 янв 2026, 09:22
92e8113
Код
Авторство
О чём код?
/* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @summary Test multi-threaded use of StringBuilder * @compile --release 8 RacingSBThreads.java * @run main/othervm -esa RacingSBThreads read * @run main/othervm -esa RacingSBThreads insert * @run main/othervm -esa RacingSBThreads append * @run main/othervm -Xcomp RacingSBThreads */ import java.nio.CharBuffer; import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; /** * Test racing accesses in StringBuilder. * Test source code should be compatible with JDK 8 to allow testing on older versions. */ public class RacingSBThreads { private static final int TIMEOUT_SEC = 1; // Duration to run each test case private static final int N = 1_000_000; // static number of iterations for writes and modifies private static final int LEN = 100_000; // Length of initial SB // Strings available to be used as the initial contents of a StringBuilder private static final String UTF16_CHARS = initString('\u1000', LEN); private static final String LATIN1_CHARS = initString('a', LEN); // Cache jtreg timeout factor to allow test to be run as a standalone main() private static final double TIMEOUT_FACTOR = Double.parseDouble(System.getProperty("test.timeout.factor", "1.0")); // Constant arguments available to be passed to StringBuilder operations private static final StringBuilder otherSB = new StringBuilder("ab\uFF21\uFF22"); private static final StringBuilder otherLongerSB = new StringBuilder("abcde\uFF21\uFF22\uFF23\uFF24\uFF25"); // Create a String with a repeated character private static String initString(char c, int len) { char[] chars = new char[len]; Arrays.fill(chars, c); return new String(chars); } // Plain unsynchronized reference to a StringBuilder // Updated by the writer thread // Read by the reader thread private StringBuilder buf; // The current stress test case private final StressKind stressKind; // Count of faults, zero if no faults found private final AtomicInteger faultCount = new AtomicInteger(0); /** * Run the stress cases indicated by command line arguments or run all cases. * Running each for TIMEOUT_SEC seconds or until a failure. * The timeout/test duration can be scaled by setting System property * `test.timeout.factor` to a double value, for example, `-Dtest.timeout.factor=2.0` * @param args command line arguments */ public static void main(String[] args) { Duration duration = Duration.ofSeconds((long)(TIMEOUT_SEC * TIMEOUT_FACTOR)); StressKind[] kinds = StressKind.values(); if (args.length > 0) { // Parse explicitly supplied StressKind arguments try { kinds = Arrays.stream(args) .map((s) -> StressKind.valueOf(s.toUpperCase(Locale.ROOT))) .toArray(StressKind[]::new); } catch (Exception ex) { System.out.println("Invalid StressKind arguments: " + Arrays.toString(args)); return; } } // Run each kind for the duration int totalFaults = 0; for (StressKind sk : kinds) { Instant end = Instant.now().plus(duration); // note clock time, not runtime while (Instant.now().isBefore(end)) { int faultCount = new RacingSBThreads(sk).stress(); if (faultCount > 0) { System.out.printf("ERROR: Test case %s, %d faults%n", sk, faultCount); } totalFaults += faultCount; } } if (totalFaults > 0) { throw new AssertionError("Total faults: " + totalFaults); } } // Enum of the various test cases with a lambda to invoke for each enum StressKind { /** * Reading characters should always be one of the known values being written to the destination */ READ(LATIN1_CHARS, (sb, chr) -> { char ch = sb.charAt(LEN * 4 / 5); if (ch != chr & ch != (chr & 0xff) & ch != chr >> 8) { throw new AssertionError("Unexpected characters in buffer: 0x" + Integer.toHexString(ch)); } }), /** * Insert another StringBuilder; in the face of racy changes to the destination */ INSERT(LATIN1_CHARS, (sb, C) -> { sb.insert(sb.length() - 1, otherLongerSB, 0, otherLongerSB.length()); }), /** * Appending a StringBuilder in the face of racy changes to the destination */ APPEND(LATIN1_CHARS, (sb, C) -> { sb.append(otherSB, 0, otherSB.length()); }), ; private final BiConsumer<StringBuilder,Character> func; private final String sbInitString; /** * Defines a test case. * @param sbInitString the initial contents of the StringBuilder; chooses the coder * @param func the test BiConsumer to apply to the StringBuilder */ private StressKind(String sbInitString, BiConsumer<StringBuilder,Character> func) { this.func = func; this.sbInitString = sbInitString; } } public RacingSBThreads(StressKind stressKind) { this.stressKind = stressKind; } /** * Run the stress case. * One thread continuously creates a StringBuilder and fills it before trimming it to zero. * The other thread performs the test case on the same StringBuilder (without any synchronization) * @return the count of faults */ private int stress() { PokeBuilder r = new PokeBuilder(this, N); Writer w = new Writer(this, N); Thread writer = new Thread(w::createShrink); Thread reader = new Thread(r::readModify); writer.start(); reader.start(); join(reader); System.out.println(r); writer.interrupt(); join(writer); System.out.println(w); return r.racing.faultCount.get(); } /** * Wait for a thread to terminate. * @param thread a thread to wait for */ private void join(Thread thread) { do { try { thread.join(); break; } catch (InterruptedException ie) { // ignore and retry } } while (true); } /** * Run a StressKind case in a loop keeping track of exceptions. * The StringBuilder under test is shared with the writer task without benefit of synchronization. */ private static class PokeBuilder { private final RacingSBThreads racing; private final int iterations; private int nulls; private int bounds; private int pokeCycles; private int bufChanges; public PokeBuilder(RacingSBThreads racing, int iterations) { this.racing = racing; this.iterations = iterations; nulls = 0; bounds = 0; pokeCycles = 0; bufChanges = 0; } // Repeatedly change the racy StringBuilder, ignoring and counting exceptions private void readModify() { System.out.println("Starting " + racing.stressKind); sleep(100); for (int i = 0; i < iterations; ++i) { pokeCycles++; StringBuilder sb = racing.buf; // read once try { if (sb.length() > Integer.MAX_VALUE / 4) { sb.setLength(Integer.MAX_VALUE / 4); } // Invoke the test case racing.stressKind.func.accept(sb, racing.stressKind.sbInitString.charAt(0)); if (sb != racing.buf) { bufChanges++; } } catch (NullPointerException e) { ++nulls; } catch (IndexOutOfBoundsException e) { ++bounds; } catch (AssertionError ae) { racing.faultCount.incrementAndGet(); throw ae; } } } private static void sleep(int i) { try { Thread.sleep(i); } catch (InterruptedException ignored) { } } public String toString() { return String.format("pokeCycles:%d, bounds:%d, bufChanges:%d, nulls=%d", pokeCycles, bounds, bufChanges, nulls); } } /** * Repeatedly create and append strings to a StringBuilder shared through fields of RacingSBThreads. * The StringBuilder is created new on each iteration and truncated at the end of each iteration. * Exceptions are counted and reported. */ private static class Writer { private final RacingSBThreads racing; private final int iterations; private int sumWriter; private int writeCycles; private int putBounds; public Writer(RacingSBThreads racing, int iterations) { this.racing = racing; this.iterations = iterations; } private void createShrink() { for (int i = 0; i < iterations; ++i) { if (i % 100_000 == 0) { if (Thread.interrupted()) { break; } } try { ++writeCycles; racing.buf = new StringBuilder(racing.stressKind.sbInitString); racing.buf.append(UTF16_CHARS); sumWriter += racing.buf.length(); racing.buf.setLength(0); racing.buf.trimToSize(); } catch (Exception ex) { ++putBounds; } } } public String toString() { return String.format("writeCycles:%d, bounds:%d, sumWriter=%d", writeCycles, putBounds, sumWriter); } } }