[
https://issues.apache.org/jira/browse/WW-5724?focusedWorklogId=1040937&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1040937
]
ASF GitHub Bot logged work on WW-5724:
--------------------------------------
Author: ASF GitHub Bot
Created on: 11/Sep/26 13:22
Start Date: 11/Sep/26 13:22
Worklog Time Spent: 10m
Work Description: lukaszlenart opened a new pull request, #1914:
URL: https://github.com/apache/struts/pull/1914
Fixes [WW-5724](https://issues.apache.org/jira/browse/WW-5724)
`AbstractLocalizedTextProvider` caches `MessageFormat` instances by pattern
and locale and returned the cached instance itself from `buildMessageFormat`.
`MessageFormat` is not thread-safe, so concurrent callers rendering the same
message formatted through one shared instance; with a date or time sub-format
that produced output for the wrong argument or an exception.
`buildMessageFormat` now returns a clone of the cached instance, so the
cached entry is only ever a template and is never formatted directly.
`MessageFormat.clone()` deep-copies the sub-formats. The pattern-parsing cache
is kept.
Two tests added to `StrutsLocalizedTextProviderTest`: one asserts two
lookups of the same pattern no longer return the same instance while the cache
still holds one entry; the other renders a `{0,date,short}` message from four
threads with distinct dates and asserts every output matches its own argument.
Both failed before the change.
Why clone rather than the alternatives: building a fresh instance per call
drops the parsing cache the WW-5540 work relies on, and synchronizing on the
shared instance serializes all rendering. Measured over 1.2M renders on JDK 17,
cloning was the cheapest of the three (176 ms, against 409 ms fresh-per-call
and 556 ms synchronized).
No configuration or API change. Applies to both maintenance lines; the 6.x
backport follows.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Issue Time Tracking
-------------------
Worklog Id: (was: 1040937)
Remaining Estimate: 0h
Time Spent: 10m
> Cached MessageFormat instances in AbstractLocalizedTextProvider are shared
> between concurrent callers
> -----------------------------------------------------------------------------------------------------
>
> Key: WW-5724
> URL: https://issues.apache.org/jira/browse/WW-5724
> Project: Struts 2
> Issue Type: Bug
> Reporter: Lukasz Lenart
> Priority: Major
> Fix For: 6.12.0, 7.4.0
>
> Time Spent: 10m
> Remaining Estimate: 0h
>
> h2. Summary
> {{AbstractLocalizedTextProvider}} caches {{java.text.MessageFormat}}
> instances keyed by pattern and locale and returns the cached instance from
> {{buildMessageFormat}}, while {{DefaultTextProvider}} builds a fresh instance
> per call. {{MessageFormat}} is not thread-safe, so callers rendering the same
> message concurrently format through one shared instance.
> h2. Current behaviour
> {{buildMessageFormat}} returns the cached instance on a hit and
> {{formatWithNullDetection}} calls {{format()}} on it directly. The provider
> is a singleton bean, so the cache and every instance in it are shared
> application-wide. The sibling {{DefaultTextProvider.getText}} constructs a
> new {{MessageFormat}} on each call.
> A pattern carrying a date or time sub-format delegates to a
> {{SimpleDateFormat}} held by the {{MessageFormat}}, which keeps per-call
> state in its {{Calendar}}; concurrent {{format()}} calls on the shared
> instance can render the wrong argument or throw. Number and choice
> sub-formats and plain placeholders are not affected on current JDKs.
> h2. Proposed change
> Keep the cache, but have {{buildMessageFormat}} return a clone of the cached
> instance, so the cached entry is only ever a template and is never formatted
> directly. {{MessageFormat.clone()}} deep-copies its sub-formats. This
> preserves the pattern-parsing cache the WW-5540 work relies on; measured
> against a fresh instance per call and against synchronizing on the shared
> instance, cloning is the cheapest of the three.
> h2. Compatibility notes
> No configuration or API change. Descendant classes that override
> {{buildMessageFormat}} keep their current behaviour; only the base
> implementation changes.
> Applies to both maintenance lines.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)