jbonofre commented on code in PR #739:
URL: https://github.com/apache/camel-karaf/pull/739#discussion_r3860502088


##########
core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiTypeConverter.java:
##########
@@ -57,6 +61,19 @@ public class OsgiTypeConverter extends ServiceSupport 
implements TypeConverter,
     private CamelContext camelContext;
     private final Injector injector;
     private final ServiceTracker<TypeConverterLoader, Object> tracker;
+    /**
+     * The loaders the tracker has handed us, kept here rather than read back 
from the tracker: resolving them
+     * through the tracker inside {@link #createRegistry()} would mean calling 
into the ServiceTracker and the
+     * framework while holding this instance's monitor.
+     */
+    private final Map<ServiceReference<TypeConverterLoader>, 
TypeConverterLoader> trackedLoaders
+            = new ConcurrentHashMap<>();
+    /**
+     * Registrations made through this facade rather than by a {@link 
TypeConverterLoader}, in the order they were
+     * made, so a rebuilt registry can be brought back to the same state. 
Discarding the delegate would otherwise
+     * drop them with no way to get them back.
+     */
+    private final List<Consumer<TypeConverterRegistry>> 
programmaticRegistrations = new CopyOnWriteArrayList<>();

Review Comment:
   **Blocking.** This list is unbounded and never pruned, which in OSGi means 
classloader retention.
   
   Every registration ever made through the facade is retained for the life of 
the `CamelContext`, and each retained lambda strongly references the 
`TypeConverter` instance it captured — and therefore the contributing bundle's 
classloader. After that bundle is uninstalled, nothing here lets go. That is 
the leak shape this repo cares most about, and it is new in this PR: before, 
discarding the delegate did at least drop the references.
   
   Reproduced: 500 `addTypeConverter`/`removeTypeConverter` pairs leave the 
list at **1000** entries, with the converter from pair 1 still strongly 
reachable.
   
   Two knock-ons worth naming:
   
   - **Rebuild cost grows monotonically.** Every rebuild replays the whole 
list, and `addTypeConverters(Object)` re-runs reflective `@Converter` scanning 
each time. Since #739 also holds the instance monitor across the rebuild, the 
container's conversion stall grows with the number of registrations the context 
has ever seen.
   - **The list has no relationship to bundle lifecycle.** A Blueprint 
container refresh re-registers, appending duplicates rather than replacing.
   
   A keyed collection — `Map<TypeConvertible<?,?>, 
Consumer<TypeConverterRegistry>>`, insertion-ordered — would fix growth, 
retention, and the `removeTypeConverter` ordering question in one move: a 
removal deletes the entry instead of appending an inverse. It does not solve 
`addTypeConverters(Object)`, which has no natural key, but that is a much 
smaller surface to think about.



##########
core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiTypeConverter.java:
##########
@@ -238,10 +291,13 @@ public TypeConverterExists getTypeConverterExists() {
 
     @Override
     public void setTypeConverterExists(TypeConverterExists 
typeConverterExists) {
-        getDelegate().setTypeConverterExists(typeConverterExists);
+        register(registry -> 
registry.setTypeConverterExists(typeConverterExists));
     }
 
-    public DefaultTypeConverter getDelegate() {
+    // fully synchronized rather than double checked: the delegate is not 
immutable after publication -
+    // removedService stops and replaces it - so a lock free read of the field 
buys a race for no real gain,
+    // conversion work dwarfing an uncontended monitor either way
+    public synchronized DefaultTypeConverter getDelegate() {

Review Comment:
   Not blocking, but the invariant the safety argument rests on does not hold, 
so I would like the comment and the PR description to stop asserting it.
   
   The PR body says there is "no call into the tracker or the framework left 
underneath this instance's monitor at all". There are several. `getDelegate()` 
is now `synchronized` and calls `ensureTrackerOpen()`, and `tracker.open()` 
does `bundleContext.addServiceListener(...)` and 
`bundleContext.getServiceReferences(...)`, then synchronously runs 
`trackInitial()` -> your own `addingService` -> `bundleContext.getService(...)` 
— all under `this`. Separately, `createRegistry()` runs foreign `loader.load()` 
under `this`.
   
   No ABBA results, because the callbacks no longer take this monitor, and (per 
your disassembly, which I verified) `Tracked` is not held across the customizer 
either. So the code is *not* deadlock-prone. But "we removed the `tracker.*` 
calls from `createRegistry()`" and "nothing framework-facing runs under our 
monitor" are different claims, and only the first one is true. Holding `this` 
across arbitrary bundle `load()` is the same shape I objected to originally; it 
is now reached by a different route.
   
   Second, on this comment specifically: "conversion work dwarfing an 
uncontended monitor" is true in steady state and misleading during a rebuild, 
which is the case that matters. While `createRegistry()` runs — core converter 
package scan plus every tracked loader's `load()` — every conversion in the 
container is blocked on this monitor, and per the `programmaticRegistrations` 
thread that duration grows over the life of the context.
   
   I am fine with the tradeoff. Please just say what it is: all conversions 
serialize behind a rebuild, and the monitor is held across foreign bundle code.



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