Skip to content

fix(#733): do not let an uninstalled bundle poison factory resolution (backport of #741) - #752

Merged
jbonofre merged 1 commit into
apache:camel-karaf-4.18.xfrom
jbonofre:backport-741-factoryfinder-4.18.x
Sep 7, 2026
Merged

fix(#733): do not let an uninstalled bundle poison factory resolution (backport of #741)#752
jbonofre merged 1 commit into
apache:camel-karaf-4.18.xfrom
jbonofre:backport-741-factoryfinder-4.18.x

Conversation

@jbonofre

@jbonofre jbonofre commented Sep 6, 2026

Copy link
Copy Markdown
Member

Backport of #741 to camel-karaf-4.18.x. Fixes #733 on that branch.

What

OsgiFactoryFinder.getResource walks bundleContext.getBundles() and returns
the first bundle with a matching factory descriptor.

getBundles() returns a snapshot, and per the OSGi spec Bundle.getEntry()
throws IllegalStateException once a bundle has been uninstalled. So a
feature:uninstall or bundle:update running concurrently with a resolution
can make the scan throw on a bundle that has nothing to do with the factory
being looked up.

What turns that transient race into a lasting failure is the caching in Camel.
findClass calls getResource from inside DefaultFactoryFinder.addToClassMap,
whose mapping function stores the exception in classesNotFoundExceptions and
rethrows it on every later lookup of the same key. IllegalStateException is an
Exception, so the key stays broken for the life of the context, until clear().

How

  • Skip bundles already in state Bundle.UNINSTALLED.
  • Treat an IllegalStateException from getEntry as "this bundle has no such
    entry" and continue, rather than letting it fail the whole scan. This covers
    the bundle that is uninstalled between the state check and the call.
  • Log at DEBUG which bundle supplied the descriptor, so an operator with several
    providers installed can tell which one is actually in use.

Selection is unchanged. The scan still breaks at the first match, so
install order still decides and there is no added cost on a path that is walked
once per key.

Cherry-pick

Clean cherry-pick of e784cd9, no conflicts and
no adaptation needed — OsgiFactoryFinder.java was identical on both branches.

Tests

OsgiFactoryFinderTest (new): no provider, single provider, first-match-wins
with several providers, an UNINSTALLED bundle never being asked for an entry,
and a bundle uninstalled mid-scan not failing the lookup.

Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 -- OsgiFactoryFinderTest
Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 -- OsgiDefaultCamelContextTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 -- OsgiTypeConverterTest

…lution (backport of apache#741)

getResource walks bundleContext.getBundles() and returns the first bundle with
a matching descriptor. getBundles() returns a snapshot, and per the OSGi spec
Bundle.getEntry() throws IllegalStateException once a bundle has been
uninstalled, so a concurrent feature:uninstall or bundle:update can make the
scan throw on a bundle that has nothing to do with the factory being resolved.

findClass calls getResource from inside DefaultFactoryFinder.addToClassMap,
which catches Exception, stores it in classesNotFoundExceptions and rethrows it
on every later lookup of the same key. That turns the transient race into a
lasting failure: the key stays broken for the life of the context, until
clear(). Skip bundles already in state UNINSTALLED, and treat an
IllegalStateException from getEntry as "this bundle has no such entry" rather
than letting it fail the whole scan.

Selection is unchanged. The scan still stops at the first match, so install
order still decides and there is no added cost on a path that is walked once
per key. Log the bundle that supplied the descriptor at DEBUG: with several
providers installed, that is what lets an operator tell which one is actually
in use.

Deliberately not warning when several bundles provide the same descriptor. The
default camel-core feature installs both camel-xml-io and camel-xml-jaxb, and
both ship META-INF/services/org/apache/camel/modelxml-dumper with a different
implementation class, so a stock install would warn on every startup. That
ambiguity is inherited from flat-classpath Camel, where classpath order picks
the winner in the same way.

Not addressed here: the rolling-upgrade half of apache#733. Once a key resolves,
addToClassMap short-circuits on classMap and getResource is never called again
for it, so a patched bundle installed afterwards is still silently ignored by
an already-running context. Catching that needs cache invalidation on bundle
events, in the spirit of OsgiTypeConverter revalidating its delegate on service
changes, and is a separate change with its own blast radius.

BundleEntry becomes package private. It was private while getResource, which
returns it, is public, so the modifier was not restricting anything; this makes
it reachable from the tests in the package.
@jbonofre
jbonofre merged commit 2f7c614 into apache:camel-karaf-4.18.x Sep 7, 2026
3 checks passed
@jbonofre
jbonofre deleted the backport-741-factoryfinder-4.18.x branch September 7, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants