Lukasz Lenart created WW-5650:
---------------------------------

             Summary: Refactor JSON plugin reader/writer to per-request 
instances instead of shared mutable state
                 Key: WW-5650
                 URL: https://issues.apache.org/jira/browse/WW-5650
             Project: Struts 2
          Issue Type: Improvement
          Components: Plugin - JSON
            Reporter: Lukasz Lenart
             Fix For: 7.3.0


h2. Background

The JSON plugin's request parser (StrutsJSONReader) and response serializer
(StrutsJSONWriter) hold per-operation working state as instance fields:

* StrutsJSONReader: character iterator, current char, current token, string 
buffer,
  and the nesting-depth counter used for the maxDepth/maxElements limits.
* StrutsJSONWriter: output buffer, cyclic-reference stack, root object, and the
  expression-path state (buildExpr / exprStack / include-exclude patterns).

Both are obtained through JSONUtil (getReader() / the injected writer), and 
JSONUtil
is held once by the singleton JSONInterceptor. As a result a single reader and 
a single
writer instance are reused across all concurrent requests handled by that 
interceptor.
Because the working state lives on those shared instances, concurrent 
parse/serialize
calls can interfere with each other, producing incorrect results and, for the 
reader,
undermining the maxDepth/maxElements limits under load.

h2. Interim fixes

PR #1775 (WW-5643) and PR #1776 (WW-5644) address the immediate correctness 
problem by
confining the per-operation state to a ThreadLocal, created fresh per call and 
cleared in
a finally block. These are correct but rely on a per-request cleanup contract 
and leave
some configuration fields shared. This ticket tracks the proper structural fix 
that
supersedes that approach.

h2. Proposed approach

Stop sharing stateful reader/writer instances. The JSONReader/JSONWriter beans 
are already
declared scope="prototype", so a fresh instance can be obtained per operation 
via the
container. Refactor JSONUtil so that:

* Each deserialize/serialize call obtains a fresh reader/writer, configures it 
(limits,
  ignoreHierarchy, dateFormat, include/exclude patterns, etc.), uses it, and 
discards it --
  all within a single scope. This removes the current configure-then-use split
  (applyLimitsToReader() + deserializeInput(); serialize() setters + write()) 
that assumes
  a stable shared instance.
* The standalone JSONUtil.getReader() accessor that returns a shared instance 
is deprecated
  / removed in favour of the per-operation flow.
* The StrutsJSONReader/StrutsJSONWriter public extension points (the protected
  next()/object()/array()/string()/add()/bean()/map() methods used by 
subclasses) remain
  unchanged, so custom reader/writer subclasses and the struts.json.reader /
  struts.json.writer overrides keep working.
* Once instances are no longer shared, the per-field ThreadLocal state 
introduced by the
  interim PRs is unwound (state returns to plain fields on the now-single-use 
instance).

Performance note: the expensive bean-introspection cache in StrutsJSONWriter is 
a static
ConcurrentMap shared across instances, so creating a fresh writer per request 
does not lose
the reflection cache and is effectively free.

h2. Scope

* plugins/json -- JSONUtil, JSONInterceptor, StrutsJSONReader, StrutsJSONWriter
* No change to public template/config surface (struts.json.reader / 
struts.json.writer,
  interceptor params).

h2. Acceptance criteria

* No reader/writer instance carries per-operation state across concurrent 
requests.
* The concurrency regression tests added in #1775 and #1776 are preserved and 
pass against
  the refactored code.
* Existing StrutsJSONReaderTest / JSONReaderTest / StrutsJSONWriterTest / 
JSONInterceptorTest
  suites pass unchanged.
* Custom reader/writer subclasses and struts.json.reader/writer overrides 
continue to work.

h2. Related

* Supersedes the interim approach in #1775 (WW-5643) and #1776 (WW-5644).




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to