Java Thread Sanitizer (https://openjdk.org/projects/tsan/) reports a data race on the java.util.Formatter.DFS field. A sample stack trace from JDK 21 is attached below.
Although discussion in https://github.com/openjdk/jdk/pull/7703#discussion_r820109284 argues that this data race is benign, I'm not convinced. The main concern is that a thread could see a partially initialized DecimalFormatSymbols object, when the check `if (dfs != null && dfs.getLocale().equals(locale))` succeeds.
It is due to the same reason why double-checked locking is broken without volatile: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html "The first reason it doesn't work".
```
WARNING: ThreadSanitizer: data race (pid=4737)
Write of size 4 at 0x000081baeaf8 by thread T12 (mutexes: write M0, write M1, write M2, write M3, write M4, write M5, write M6, write M7, write M8, write M9, write M10):
#0 java.util.Formatter.getDecimalFormatSymbols(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols; Formatter.java:2034
#1 java.util.Formatter.getZero(Ljava/util/Locale;)C Formatter.java:2040
#2 java.util.Formatter$FormatSpecifier.localizedMagnitude(Ljava/util/Formatter;Ljava/lang/StringBuilder;Ljava/lang/CharSequence;IIILjava/util/Locale;)Ljava/lang/StringBuilder; Formatter.java:4531
#3 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;JLjava/util/Locale;)V Formatter.java:3377
#4 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;ILjava/util/Locale;)V Formatter.java:3362
#5 java.util.Formatter$FormatSpecifier.printInteger(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3060
#6 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3021
#7 java.util.Formatter.format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2791
#8 java.util.Formatter.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2728
#9 java.lang.String.format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; String.java:4431
...
Previous read of size 4 at 0x000081baeaf8 by thread T147 (mutexes: write M11, write M12, write M13, write M14, write M15):
#0 java.util.Formatter.getDecimalFormatSymbols(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols; Formatter.java:2026
#1 java.util.Formatter.getZero(Ljava/util/Locale;)C Formatter.java:2040
#2 java.util.Formatter$FormatSpecifier.localizedMagnitude(Ljava/util/Formatter;Ljava/lang/StringBuilder;Ljava/lang/CharSequence;IIILjava/util/Locale;)Ljava/lang/StringBuilder; Formatter.java:4531
#3 java.util.Formatter$FormatSpecifier.localizedMagnitude(Ljava/util/Formatter;Ljava/lang/StringBuilder;JIILjava/util/Locale;)Ljava/lang/StringBuilder; Formatter.java:4520
#4 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/lang/StringBuilder;Ljava/time/temporal/TemporalAccessor;CLjava/util/Locale;)Ljava/lang/Appendable; Formatter.java:4362
#5 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/time/temporal/TemporalAccessor;CLjava/util/Locale;)V Formatter.java:4282
#6 java.util.Formatter$FormatSpecifier.printDateTime(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3105
#7 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3014
#8 java.util.Formatter.format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2797
#9 java.util.Formatter.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2728
#10 java.lang.String.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; String.java:4390
...
```
Although discussion in https://github.com/openjdk/jdk/pull/7703#discussion_r820109284 argues that this data race is benign, I'm not convinced. The main concern is that a thread could see a partially initialized DecimalFormatSymbols object, when the check `if (dfs != null && dfs.getLocale().equals(locale))` succeeds.
It is due to the same reason why double-checked locking is broken without volatile: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html "The first reason it doesn't work".
```
WARNING: ThreadSanitizer: data race (pid=4737)
Write of size 4 at 0x000081baeaf8 by thread T12 (mutexes: write M0, write M1, write M2, write M3, write M4, write M5, write M6, write M7, write M8, write M9, write M10):
#0 java.util.Formatter.getDecimalFormatSymbols(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols; Formatter.java:2034
#1 java.util.Formatter.getZero(Ljava/util/Locale;)C Formatter.java:2040
#2 java.util.Formatter$FormatSpecifier.localizedMagnitude(Ljava/util/Formatter;Ljava/lang/StringBuilder;Ljava/lang/CharSequence;IIILjava/util/Locale;)Ljava/lang/StringBuilder; Formatter.java:4531
#3 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;JLjava/util/Locale;)V Formatter.java:3377
#4 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;ILjava/util/Locale;)V Formatter.java:3362
#5 java.util.Formatter$FormatSpecifier.printInteger(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3060
#6 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3021
#7 java.util.Formatter.format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2791
#8 java.util.Formatter.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2728
#9 java.lang.String.format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; String.java:4431
...
Previous read of size 4 at 0x000081baeaf8 by thread T147 (mutexes: write M11, write M12, write M13, write M14, write M15):
#0 java.util.Formatter.getDecimalFormatSymbols(Ljava/util/Locale;)Ljava/text/DecimalFormatSymbols; Formatter.java:2026
#1 java.util.Formatter.getZero(Ljava/util/Locale;)C Formatter.java:2040
#2 java.util.Formatter$FormatSpecifier.localizedMagnitude(Ljava/util/Formatter;Ljava/lang/StringBuilder;Ljava/lang/CharSequence;IIILjava/util/Locale;)Ljava/lang/StringBuilder; Formatter.java:4531
#3 java.util.Formatter$FormatSpecifier.localizedMagnitude(Ljava/util/Formatter;Ljava/lang/StringBuilder;JIILjava/util/Locale;)Ljava/lang/StringBuilder; Formatter.java:4520
#4 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/lang/StringBuilder;Ljava/time/temporal/TemporalAccessor;CLjava/util/Locale;)Ljava/lang/Appendable; Formatter.java:4362
#5 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/time/temporal/TemporalAccessor;CLjava/util/Locale;)V Formatter.java:4282
#6 java.util.Formatter$FormatSpecifier.printDateTime(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3105
#7 java.util.Formatter$FormatSpecifier.print(Ljava/util/Formatter;Ljava/lang/Object;Ljava/util/Locale;)V Formatter.java:3014
#8 java.util.Formatter.format(Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2797
#9 java.util.Formatter.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/util/Formatter; Formatter.java:2728
#10 java.lang.String.format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; String.java:4390
...
```
- relates to
-
JDK-8282625 Formatter caches Locale/DecimalFormatSymbols poorly
- Resolved