oscerd opened a new issue, #743:
URL: https://github.com/apache/camel-karaf/issues/743

   Split out of the review on #739, at @jbonofre's suggestion, so it does not 
grow that PR. Two symptoms, one underlying cause: the delegate is invalidated 
and stopped outside the monitor that `getDelegate()` takes, while callers 
already hold a reference to it.
   
   ## 1. An invalidation can be swallowed
   
   `removedService` writes `this.delegate = null` with no monitor held, so 
`synchronized getDelegate()` gives mutual exclusion between readers only, never 
against that writer:
   
   ```
   A: getDelegate() takes `this`, delegate == null, enters createRegistry()
      (loader L has already been loaded into the new registry)
   B: removedService(L) -> stopService(this.delegate)  // still null, no-op
                        -> this.delegate = null        // no monitor held
   A: delegate = <registry built with L in it>
   ```
   
   The invalidation lands between A's read and A's write and is lost. 
Converters belonging to an unregistered bundle keep serving indefinitely, and 
no later event invalidates them.
   
   Reproduced during the review by stalling at the end of `createRegistry()`: 
after `removedService` returns, `getDelegate()` hands back the same instance 
that was built with the removed loader in it, and `assertSame(built, 
afterRemoval)` passes.
   
   This predates #739 — the old unsynchronized `if (delegate == null)` had the 
same lost write — but #739 makes the window **wider**, not narrower: the 
monitor is now held across the whole rebuild, the core converter scan plus 
every `loader.load()`, so there is much more time for a `removedService` to 
land inside it.
   
   An epoch counter would close it: read a generation before 
`createRegistry()`, re-check it before the assign, discard and retry if 
`removedService` bumped it meanwhile.
   
   ## 2. The outgoing delegate is stopped while callers still hold it
   
   `getDelegate()` hands the delegate to a caller and returns; the caller then 
uses it after the monitor is released. `removedService` can call 
`ServiceHelper.stopService` on that same instance a moment later, so the caller 
converts against a stopped registry.
   
   Making `getDelegate()` fully `synchronized` in #739 narrows this window but 
does not close it — the use is outside the monitor either way, so no locking 
discipline on `getDelegate()` alone can fix it.
   
   Closing it means not stopping the outgoing delegate eagerly: let the old 
instance keep serving in-flight conversions and let it go when nothing 
references it.
   
   ## Why together
   
   Both are the same shape — mutation of an instance that callers already hold, 
performed outside the monitor that publishes it — and a fix for one constrains 
the fix for the other. Doing them in one change avoids designing the 
invalidation twice.
   
   Neither is a regression from #739; that PR is not blocked on this.
   
   ---
   _Claude Code on behalf of Andrea Cosentino_
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to