[
https://issues.apache.org/jira/browse/WW-5832?focusedWorklogId=1051644&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1051644
]
ASF GitHub Bot logged work on WW-5832:
--------------------------------------
Author: ASF GitHub Bot
Created on: 31/Oct/26 16:10
Start Date: 31/Oct/26 16:10
Worklog Time Spent: 10m
Work Description: g0w6y opened a new pull request, #2090:
URL: https://github.com/apache/struts/pull/2090
Fixes [WW-5832](https://issues.apache.org/jira/browse/WW-5832)
With `struts.freemarker.mru.max.strong.size=0` (the default)
`FreemarkerManager` leaves FreeMarker's template cache on its default
`SoftCacheStorage`, which has no entry limit. The cache key includes the
locale. For `LocaleProvider` actions (e.g. `ActionSupport`)
`FreemarkerResult.deduceLocale()` returns the action's locale, which by default
comes from `request.getLocale()` and is not validated unless
`struts.locale.validateRequestLocale` is enabled (default `false`). Distinct
request locales therefore add template cache entries without a count bound.
Same shape as WW-5668 (text caches) and WW-5716 (Tiles locale caches).
The cost is retention and re-parsing, not an out-of-memory error: entries
are soft references and are cleared under heap pressure, which also evicts the
templates legitimate requests need, and every locale re-reads and re-parses the
template.
### Change
- When `mruMaxStrongSize` is `0`, install a `SoftCacheStorage` subclass that
is emptied once it holds 10000 entries (the same cap as
`struts.i18n.cacheMaxSize`). Entries stay soft references, and an evicted
template is just loaded again on next use. The bound is approximate under
concurrency.
- Not `soft:10000`: that creates `MruCacheStorage(0, 10000)`, which is not a
`ConcurrentCacheStorage`, so `TemplateCache` would take a lock on every lookup;
the default `SoftCacheStorage` avoids that (see WW-3766).
- The user-configured path (`mruMaxStrongSize > 0` -> `strong:N`) is
unchanged.
10000 entries is templates x locales: core ships 131 theme templates (at
most 45 in one theme), so an application would need on the order of a hundred
locales across a few hundred templates to reach it.
## How verified
`FreemarkerManagerTest#testTemplateCacheIsBoundedByDefault` loads one
template under 10100 distinct locales and asserts the cache stays within the
limit and the storage is still concurrent. With the bounded-storage branch
removed it fails on the size assertion; with the change it passes.
`FreemarkerManagerTest` (8), `FreeMarkerResultTest` (6) and
`I18nInterceptorTest` (28) pass, and the full `core` suite runs 3626 tests with
0 failures and 2 skipped.
Also checked against a running Struts Boot app with an `ActionSupport`
action rendering a FreeMarker template and 12,000 requests with 12,000 distinct
`Accept-Language` headers: on `main` the template cache grew from 1 to 12,001
entries (`DefaultSoftCacheStorage`); with this change it is a
`BoundedSoftCacheStorage`, emptied at 10000 and holding 2,002 entries after the
same run. All 12,000 responses were correct and the server logged no errors.
### Not changed
- `struts.locale.validateRequestLocale` and its default,
`AcceptLanguageLocaleHandler`, and the `default.properties` comment are
untouched.
- POJO actions (not `LocaleProvider`) use `configuration.getLocale()` in
`deduceLocale()`; I did not test them.
- The limit is not configurable; happy to add a constant if you want one.
Issue Time Tracking
-------------------
Worklog Id: (was: 1051644)
Remaining Estimate: 0h
Time Spent: 10m
> FreeMarker template cache is not bounded by default and is keyed on the
> request-derived locale
> ----------------------------------------------------------------------------------------------
>
> Key: WW-5832
> URL: https://issues.apache.org/jira/browse/WW-5832
> Project: Struts 2
> Issue Type: Bug
> Components: Core
> Affects Versions: 6.12.0, 7.0.0, 7.3.0, 7.4.0
> Reporter: Gouri Sankar A
> Priority: Minor
> Time Spent: 10m
> Remaining Estimate: 0h
>
> With struts.freemarker.mru.max.strong.size=0 (the default in
> default.properties), FreemarkerManager.createConfiguration does not set a
> cache storage, so FreeMarker's template cache uses its default
> SoftCacheStorage, which has no entry limit. The cache key includes the
> locale. For actions that provide a locale (LocaleProvider, for example
> ActionSupport), FreemarkerResult uses the ActionContext locale, which by
> default comes from the request (Accept-Language). Every distinct locale
> therefore adds template cache entries, with no count bound. Same class as
> WW-5668 (localized-text caches, capped at 10000) and WW-5716 (Tiles locale
> caches, capped at 1000).
> Reproduction (main, against a running Struts Boot app): an ActionSupport
> action rendering a FreeMarker template, requested with 12,000 distinct
> Accept-Language headers. The template cache (Configuration.getCacheStorage(),
> a DefaultSoftCacheStorage) grew from 1 to 12,001 entries. Repeating the same
> locales added none.
> Impact: the entries are soft references, so this alone does not exhaust the
> heap; they are kept until the GC clears them, and each locale re-reads and
> re-parses the template.
> Affected versions: the same createConfiguration code (no cache storage is set
> when the value is 0, the default) is present in the 6.12.0, 7.0.0, 7.3.0 and
> 7.4.0 tags, by source inspection. The reproduction was only run on main.
> Fix: when mru.max.strong.size is 0, use a SoftCacheStorage subclass that
> empties itself at 10000 entries. It stays a ConcurrentCacheStorage, so
> lookups remain lock-free (soft:10000 would create an MruCacheStorage, which
> TemplateCache synchronizes on, see WW-3766). The strong:N path is unchanged.
> The bound is approximate under concurrency.
> Tests: FreemarkerManagerTest#testTemplateCacheIsBoundedByDefault (fails
> without the change, passes with it). With the fix, the same 12,000-locale run
> on a live app ends with 2,002 entries instead of 12,001.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)