[ 
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)

Reply via email to