[ 
https://issues.apache.org/jira/browse/TOMEE-4650?focusedWorklogId=1036157&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1036157
 ]

ASF GitHub Bot logged work on TOMEE-4650:
-----------------------------------------

                Author: ASF GitHub Bot
            Created on: 17/Aug/26 18:50
            Start Date: 17/Aug/26 18:50
    Worklog Time Spent: 10m 
      Work Description: rzo1 commented on PR #2847:
URL: https://github.com/apache/tomee/pull/2847#issuecomment-5318835173

   Currently short on time, so AI review only: I ran an adversarial review pass 
over this PR, followed by a second pass whose job was to *refute* the first 
one's findings against the actual code. Everything below survived that second 
pass (two findings did not and are listed at the end as explicitly dismissed). 
Take it as input, not as a verdict — I have not run the build.
   
   **Overall: small and mostly right.** `Assembler.destroyApplication` (~line 
2552) is the only production caller of `ReloadableEntityManagerFactory.close()` 
in the tree, and the previous behaviour meant an application-initiated close 
made undeploy throw — which also skipped 
`persistenceClassLoaderHandler.destroy(...)` and `remf.unregister()`, leaking 
the transformer registration and the MBean. Guarding fixes that, and the 
delegate `isOpen()` semantics hold for OpenJPA 
(`!DelegatingBrokerFactory.isClosed()`), EclipseLink and Hibernate, so no 
distribution-specific breakage. There is no second half in tomee-catalina: 
`TomcatWebAppBuilder` only calls `overrideClassLoader`/`createDelegate`, never 
`close`. The new test is non-vacuous — it fails against unpatched code.
   
   My reservation is placement, not intent.
   
   ### 1. The guard is on the public EMF contract rather than the one call site 
(minor)
   
   `ReloadableEntityManagerFactory` is not an internal-only helper: it is the 
object bound at `openejb/PersistenceUnit/<id>` (`Assembler:931`), and 
`JndiEncBuilder:396-406` binds a plain `IntraVmJndiReference` to that same name 
for every `persistenceUnitRef` with no wrapper, while 
`TomcatJndiBuilder.mergeRef(PersistenceUnitReferenceInfo)` (~:607) does 
`setResource(resource, factory)` with the raw REMF. So an application injecting 
`@PersistenceUnit EntityManagerFactory` really does hold this instance. 
(Contrast `persistenceContextRef`s, which are wrapped in `JtaEntityManager` — 
only the EMF ref exposes the REMF.)
   
   `jakarta.persistence.EntityManagerFactory#close()` is specified to throw 
`IllegalStateException` when already closed, and every provider TomEE ships 
honours that. After this patch, that exception is gone for application callers 
too, not just for `Assembler.destroyApplication`.
   
   Since the problem exists at exactly one production call site, the 
containable fix is there — `if (remf.isOpen()) { remf.close(); }`, or a 
try/catch around that single call — which restores the undeploy path without 
changing the contract of a public `EntityManagerFactory` implementation.
   
   Two things the refutation pass corrected in the original write-up, so nobody 
over-weights this: closing a container-managed EMF from application code is 
itself illegal per the EE/JPA contract, so the swallowed exception only occurs 
on already-illegal usage. And the TCK argument in the PR body cannot be 
evaluated from this repo — there is no JPA TCK harness under `tck/` (bval, cdi, 
concurrency, data, jsonb, jsonp, security, jax-rs, microprofile) and no 
`persistence-javatest.txt` in the tree — so "the exclusion can be cleared once 
this merges" is unverifiable here. If that exclusion lives elsewhere, it would 
be good to confirm the vehicle does not assert the `IllegalStateException` this 
patch now swallows.
   
   ### 2. The patch removes only one of several ways undeploy skips 
`destroy()`/`unregister()` (minor)
   
   `Assembler.java:2547-2557` wraps `remf.close()`, 
`persistenceClassLoaderHandler.destroy(unitInfo.id)` and `remf.unregister()` in 
a *single* `try/catch (Throwable)`. Any failure inside a provider's `close()` — 
not just the already-closed `IllegalStateException` this PR guards — still 
jumps to the catch and leaks the classloader transformer registration and the 
JMX MBean for that persistence unit.
   
   Giving `remf.close()` its own try/catch, or reordering so 
`destroy()`/`unregister()` always run, is what actually makes undeploy robust — 
and it is the same one-call-site edit that avoids touching the public `close()` 
contract in finding 1. Worth doing both in one go.
   
   ### 3. `close()` leaves the closed delegate in place (nit, pre-existing)
   
   `ReloadableEntityManagerFactory.close()` never nulls `delegate` nor marks 
the factory closed, so `delegate()` still returns the non-null closed instance 
afterwards and `createEntityManager()`/`getProperties()`/`getCache()` hand 
callers a closed provider EMF that fails deep inside the provider rather than 
at the boundary. Pre-existing rather than introduced here, but it is the same 
object-lifecycle gap the PR is papering over — clearing the field in `close()` 
would make the new guard unnecessary.
   
   ### Checked and dismissed
   
   - **TOCTOU on the volatile `delegate` field** (three reads in `if (delegate 
!= null && delegate.isOpen()) { delegate.close(); }`). Pre-patch `close()` 
already did two independent reads, so the "read non-null → `createDelegate()` 
swaps → close the new delegate" interleaving was already possible; the extra 
`isOpen()` read changes nothing about which instance can be closed. The named 
concurrent caller does not fit either: `TomcatWebAppBuilder:1423-1435` only 
calls `createDelegate()` when 
`unitInfo.webappName.equals(webAppInfo.moduleId)`, i.e. for the one webapp that 
owns that PU during its own deployment. Capturing the field in a local is a 
fine tidy-up, but it is not a defect this PR creates.
   - **The new test's quality.** It is non-vacuous (2 close calls vs 1 against 
unpatched code) and well-formed — `EntityManagerFactoryCallable`'s 5-arg 
constructor, `PersistenceUnitInfoImpl.setLazilyInitialized` and 
`Reflections.set` all match, and `lazilyInitialized` keeps the constructor from 
building a real EMF, so it exercises precisely the changed branch. Demanding an 
`Assembler.destroyApplication` integration test for a two-token guard would be 
disproportionate.
   




Issue Time Tracking
-------------------

    Worklog Id:     (was: 1036157)
    Time Spent: 40m  (was: 0.5h)

> Undeploy closes an already-closed EntityManagerFactory
> ------------------------------------------------------
>
>                 Key: TOMEE-4650
>                 URL: https://issues.apache.org/jira/browse/TOMEE-4650
>             Project: TomEE
>          Issue Type: Bug
>            Reporter: Markus Jung
>            Assignee: Markus Jung
>            Priority: Major
>          Time Spent: 40m
>  Remaining Estimate: 0h
>
> When a test closes a container-managed {{EntityManagerFactory}} (EMF) itself, 
> TomEE's undeploy path calls {{close()}} on it again. 
> {{Assembler.destroyApplication}} then fails with "Attempting to execute an 
> operation on a closed EntityManagerFactory". The test method itself passes; 
> only the undeploy step after it errors. TomEE must check whether the EMF is 
> already closed before calling {{close()}} on it during undeploy.
> h2. Steps to reproduce / TCK reference
> * 
> {{ee.jakarta.tck.persistence.core.entityManagerFactoryCloseExceptions.ClientPmservletTest}}
>  and {{ClientPuservletTest}} — excluded in 
> {{runner-webprofile/exclusions/persistence-javatest.txt}} in the 
> apache/tomee-tck harness repo. The {{exceptionsTest}} methods pass; the class 
> reports an undeploy error.
> Remove the matching lines from {{persistence-javatest.txt}} once fixed.
> h2. Scope note
> This issue originally also covered the missing Jakarta Persistence 3.2 CDI 
> qualifier beans ({{UnsatisfiedResolutionException}} for a qualified 
> {{EntityManagerFactory}} / {{EntityManager}} / {{PersistenceUnitUtil}}). That 
> half has been split out into TOMEE-4661, because the two are independent and 
> the CDI work needs more review. The {{persistence-servlet.txt}} exclusion for 
> {{ServletEMLookupTest}} belongs to TOMEE-4661 and must stay until that is 
> fixed.
> PR for this half: https://github.com/apache/tomee/pull/2847



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

Reply via email to