Skip to content

Make StringQuoter date parsing thread-safe#10322

Merged
niloc132 merged 4 commits into
gwtproject:mainfrom
metsw24-max:stringquoter-date-threadsafe
Jun 25, 2026
Merged

Make StringQuoter date parsing thread-safe#10322
niloc132 merged 4 commits into
gwtproject:mainfrom
metsw24-max:stringquoter-date-threadsafe

Conversation

@metsw24-max

Copy link
Copy Markdown
Contributor

RequestFactory and AutoBean decode Date values on the server through StringQuoter.tryParseDate, which shared two static SimpleDateFormat instances across all request threads. SimpleDateFormat parsing mutates internal Calendar state, so concurrent requests race and return wrong dates or throw. Give each thread its own formatter via ThreadLocal.

RequestFactory and AutoBean decode Date values on the server through StringQuoter.tryParseDate, which shared two static SimpleDateFormat instances across all request threads. SimpleDateFormat parsing mutates internal Calendar state, so concurrent requests race and return wrong dates or throw. Give each thread its own formatter via ThreadLocal.
@niloc132

Copy link
Copy Markdown
Member

Interesting - the code is definitely wrong, but does this actually fail in any real use case? It looks like you could manufacture such a scenario by manually writing JSON, but clearly AutoBeans (and thus, RequestFactory) won't serialize dates using these formats. I imagine these were speculatively added, but never actually used.

public Splittable encode(Object value) {
return StringQuoter.create(String.valueOf(((Date) value).getTime()));
}

That seems contradicted by discussion at #6330 (esp #6330 (comment)) and 3df95e6. Nevertheless, there are still no tests using date strings that I can find.

I'm apprehensive about merging this despite its correctness, given the abandoned #10307 and the behavior of the account (many PRs to many unrelated repos with weak followup and tendency towards LLM-like content).

@vjay82

vjay82 commented May 27, 2026

Copy link
Copy Markdown

If the code was never used, then it could be removed, no? However, if it is in use, it looks more correct than before, proposed by a LLM or not, no?

@niloc132

Copy link
Copy Markdown
Member

Right - the code looks right, but the use case needs tests. The linked issue makes me suspect it is (or at least "was") used in some capacity, so removing a feature of a public api is at least somewhat dangerous, and there's probably no downside... but especially if it is tool-generated code, it should come with a test.

As a maintainer, I think it is appropriate for me to have a degree of skepticism for "more correct than before" code coming from low-effort sources, as a way to gain trust and attack the project. I'm definitely not accusing @metsw24-max here - the fixes to all the various repos they've sent code to seem well-intentioned, but very few have tests or actual use cases.

@metsw24-max

Copy link
Copy Markdown
Contributor Author

Fair point on the missing tests. Pushed a JRE test (StringQuoterJreTest) that covers the millis, ISO-8601, Z-suffix, RFC 2822, and unparseable paths, plus a concurrent hammer on tryParseDate that fires 16 threads x 2000 parses at the same date string. On the unpatched code it reliably produces wrong Dates (got ~11 on my box); with the ThreadLocal it sits at zero. Also wired into AutoBeanSuite.

@niloc132

Copy link
Copy Markdown
Member

Thanks - locally I haven't had the test pass without the fix, though I did see only one wrong date value (out of 16*2000 checks) - usually single digit, but rather less than 5, so the test does what it is there to do.

@zbynek

zbynek commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Why wasn't this caught by https://errorprone.info/bugpattern/DateFormatConstant ? The rule exists in 2.33.

Comment thread user/test/com/google/web/bindery/autobean/vm/StringQuoterJreTest.java Outdated
@niloc132

Copy link
Copy Markdown
Member

@zbynek nice find - it looks like that is configured as a warning rather than an error, and common.ant.xml sets nowarn so we don't get warnings in the logs. There are a lot of warnings though... some might be valid, but a large number of them are javadoc or deprecation related.

google/error-prone#424 exists to let projects pick out warnings to disable or let them fail the build, but hasn't been fixed.

When I turn warnings on, I don't see the DateFormatConstant in the output - we might have too many warnings to see all of them...

Here's a count of each warning type, via ant clean dist-dev | grep bugpattern | sort -nr | uniq -c | sort -nr

   4679     [javac]     (see https://errorprone.info/bugpattern/MissingOverride)
    341     [javac]     (see https://errorprone.info/bugpattern/JavaUtilDate)
    284     [javac]     (see https://errorprone.info/bugpattern/ReferenceEquality)
    248     [javac]     (see https://errorprone.info/bugpattern/InvalidInlineTag)
    241     [javac]     (see https://errorprone.info/bugpattern/NonApiType)
    213     [javac]     (see https://errorprone.info/bugpattern/UnnecessaryParentheses)
    198     [javac]     (see https://errorprone.info/bugpattern/InlineMeSuggester)
    146     [javac]     (see https://errorprone.info/bugpattern/SameNameButDifferent)
    143     [javac]     (see https://errorprone.info/bugpattern/JdkObsolete)
    137     [javac]     (see https://errorprone.info/bugpattern/UnusedVariable)
    116     [javac]     (see https://errorprone.info/bugpattern/BadImport)
     89     [javac]     (see https://errorprone.info/bugpattern/StringSplitter)
     75     [javac]     (see https://errorprone.info/bugpattern/DefaultCharset)
     72     [javac]     (see https://errorprone.info/bugpattern/IdentityHashMapUsage)
     71     [javac]     (see https://errorprone.info/bugpattern/OperatorPrecedence)
     67     [javac]     (see https://errorprone.info/bugpattern/NotJavadoc)
     65     [javac]     (see https://errorprone.info/bugpattern/InvalidLink)
     64     [javac]     (see https://errorprone.info/bugpattern/TypeParameterUnusedInFormals)
     63     [javac]     (see https://errorprone.info/bugpattern/ProtectedMembersInFinalClass)
     59     [javac]     (see https://errorprone.info/bugpattern/AnnotateFormatMethod)
     55     [javac]     (see https://errorprone.info/bugpattern/InvalidBlockTag)
     43     [javac]     (see https://errorprone.info/bugpattern/UnrecognisedJavadocTag)
     35     [javac]     (see https://errorprone.info/bugpattern/ClassCanBeStatic)
     34     [javac]     (see https://errorprone.info/bugpattern/ClassNewInstance)
     33     [javac]     (see https://errorprone.info/bugpattern/InvalidParam)
     32     [javac]     (see https://errorprone.info/bugpattern/StaticAssignmentInConstructor)
     24     [javac]     (see https://errorprone.info/bugpattern/StringCharset)
     22     [javac]     (see https://errorprone.info/bugpattern/FallThrough)
     20     [javac]     (see https://errorprone.info/bugpattern/MissingCasesInEnumSwitch)
     18     [javac]     (see https://errorprone.info/bugpattern/ImmutableEnumChecker)
     17     [javac]     (see https://errorprone.info/bugpattern/EqualsGetClass)
     16     [javac]     (see https://errorprone.info/bugpattern/BoxedPrimitiveConstructor)
     14     [javac]     (see https://errorprone.info/bugpattern/OutlineNone)
     12     [javac]     (see https://errorprone.info/bugpattern/InlineFormatString)
     11     [javac]     (see https://errorprone.info/bugpattern/ReturnAtTheEndOfVoidFunction)
     11     [javac]     (see https://errorprone.info/bugpattern/MixedMutabilityReturnType)
     11     [javac]     (see https://errorprone.info/bugpattern/LongDoubleConversion)
     11     [javac]     (see https://errorprone.info/bugpattern/DoNotCallSuggester)
     11     [javac]     (see https://errorprone.info/bugpattern/CatchAndPrintStackTrace)
     10     [javac]     (see https://errorprone.info/bugpattern/FutureReturnValueIgnored)
      9     [javac]     (see https://errorprone.info/bugpattern/UnusedMethod)
      9     [javac]     (see https://errorprone.info/bugpattern/SynchronizeOnNonFinalField)
      9     [javac]     (see https://errorprone.info/bugpattern/InlineMeInliner)
      8     [javac]     (see https://errorprone.info/bugpattern/InconsistentCapitalization)
      8     [javac]     (see https://errorprone.info/bugpattern/HidingField)
      6     [javac]     (see https://errorprone.info/bugpattern/URLEqualsHashCode)
      6     [javac]     (see https://errorprone.info/bugpattern/SuperEqualsIsObjectEquals)
      6     [javac]     (see https://errorprone.info/bugpattern/ReturnFromVoid)
      6     [javac]     (see https://errorprone.info/bugpattern/EscapedEntity)
      6     [javac]     (see https://errorprone.info/bugpattern/EqualsUnsafeCast)
      5     [javac]     (see https://errorprone.info/bugpattern/UnsynchronizedOverridesSynchronized)
      5     [javac]     (see https://errorprone.info/bugpattern/OrphanedFormatString)
      4     [javac]     (see https://errorprone.info/bugpattern/UnusedTypeParameter)
      4     [javac]     (see https://errorprone.info/bugpattern/UnusedNestedClass)
      4     [javac]     (see https://errorprone.info/bugpattern/TypeParameterShadowing)
      4     [javac]     (see https://errorprone.info/bugpattern/TypeEquals)
      4     [javac]     (see https://errorprone.info/bugpattern/ShortCircuitBoolean)
      4     [javac]     (see https://errorprone.info/bugpattern/OverrideThrowableToString)
      4     [javac]     (see https://errorprone.info/bugpattern/NarrowCalculation)
      4     [javac]     (see https://errorprone.info/bugpattern/LoopOverCharArray)
      4     [javac]     (see https://errorprone.info/bugpattern/IntLongMath)
      4     [javac]     (see https://errorprone.info/bugpattern/DateFormatConstant)
      4     [javac]     (see https://errorprone.info/bugpattern/DateChecker)
      3     [javac]     (see https://errorprone.info/bugpattern/UnicodeEscape)
      3     [javac]     (see https://errorprone.info/bugpattern/UndefinedEquals)
      3     [javac]     (see https://errorprone.info/bugpattern/ToStringReturnsNull)
      2     [javac]     (see https://errorprone.info/bugpattern/StaticAssignmentOfThrowable)
      2     [javac]     (see https://errorprone.info/bugpattern/ModifyCollectionInEnhancedForLoop)
      2     [javac]     (see https://errorprone.info/bugpattern/LabelledBreakTarget)
      2     [javac]     (see https://errorprone.info/bugpattern/JavaLangClash)
      2     [javac]     (see https://errorprone.info/bugpattern/IncrementInForLoopAndHeader)
      2     [javac]     (see https://errorprone.info/bugpattern/EqualsIncompatibleType)
      2     [javac]     (see https://errorprone.info/bugpattern/EmptyTopLevelDeclaration)
      2     [javac]     (see https://errorprone.info/bugpattern/ArgumentSelectionDefectChecker)
      1     [javac]     (see https://errorprone.info/bugpattern/WaitNotInLoop)
      1     [javac]     (see https://errorprone.info/bugpattern/ThreadPriorityCheck)
      1     [javac]     (see https://errorprone.info/bugpattern/InvalidThrows)
      1     [javac]     (see https://errorprone.info/bugpattern/InlineTrivialConstant)
      1     [javac]     (see https://errorprone.info/bugpattern/Finally)
      1     [javac]     (see https://errorprone.info/bugpattern/AlmostJavadoc)

The four DateFormatConstant issues are this one class, repeated twice.

To repro this list:

diff --git a/common.ant.xml b/common.ant.xml
index d5e0312342..c10d87c16c 100755
--- a/common.ant.xml
+++ b/common.ant.xml
@@ -55,7 +55,7 @@
   <property name="javac.debuglevel" value="lines,vars,source"/>
   <property name="javac.encoding" value="utf-8"/>
   <property name="javac.release" value="11"/>
-  <property name="javac.nowarn" value="true"/>
+  <property name="javac.nowarn" value="false"/>
 
   <!-- javac and errorprone instructions from https://errorprone.info/docs/installation#ant -->
   <path id="errorprone.processorpath.ref">
@@ -184,6 +184,8 @@
         <compilerarg value="-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" />
         <compilerarg value="-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED" />
         <compilerarg value="-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED" />
+        <compilerarg value="-Xmaxwarns" />
+        <compilerarg value="99999" />
         <compilerarg value="-XDcompilePolicy=simple"/>
         <compilerarg value="-processorpath"/>
         <compilerarg pathref="mergedprocessorpath.ref"/>

@metsw24-max

Copy link
Copy Markdown
Contributor Author

Bumping this one. The harness rework is in 9efec11 and covers jnehlmeier's four points along with zbynek's string-literal note. It still fails reliably without the ThreadLocal and stays clean with it, which lines up with what you saw locally.

From the errorprone thread: this change also clears the DateFormatConstant warnings for this class, since those are flagging the two shared SimpleDateFormat constants the PR replaces. Happy to make any further tweaks needed to land it.

@niloc132 niloc132 requested a review from jnehlmeier June 16, 2026 15:47

@jnehlmeier jnehlmeier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. After the copyright change I am fine with it.

@@ -0,0 +1,118 @@
/*
* Copyright 2026 Google Inc.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given it is a new file it should be Copyright 2026 GWT Project Authors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, header now reads Copyright 2026 GWT Project Authors. Pushed in dbf5570.

zbynek added a commit that referenced this pull request Jun 25, 2026
Used ErrorProne's patch functionality to add override annotations where
possible. Used regular expressions to make sure overrides are on a
separate line.

This should make it easier to show errorprone warnings during
compilation without going through much spam -- this is the most common
errorprone as pointed out in
#10322 (comment).
@zbynek zbynek requested a review from jnehlmeier June 25, 2026 19:42
@niloc132 niloc132 merged commit 140c328 into gwtproject:main Jun 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants