Skip to content

Three single-arg ServiceLoader lookups latch the thread context classloader (ElasticClientFactory, LicenseRefreshStrategyFactory, ExtensionRegistry) — two fail silently under classloader-isolating embedders #258

Description

@fupelaqu

Summary

Three ServiceLoader lookups in this repo use the single-arg ServiceLoader.load(cls) form, which resolves through the thread context classloader and latches the result. Any embedder that first touches one of these objects while a foreign/blind context classloader is installed (Tableau, plugin/OSGi-style hosts, app servers) gets an empty provider list — permanently for that classloader — and two of the three sites degrade silently.

Found specifying and implementing Epic 20 Story 20.1 (jdbc#32 / arrow#166). The in-process JDBC and ADBC drivers now guard themselves with a context-classloader swap around every entry point, which makes these latches harmless for those two drivers — but any other embedder of softclient4es-core can still latch a blind loader. Filing pre-approved by the lead (2026-09-02, story 20.1 OQ-1).

The three latch sites

Site Shape Empty provider list ⇒
core/src/main/scala/app/softnetwork/elastic/client/spi/ElasticClientFactory.scala:42-43 single-arg ServiceLoader.load(classOf[ElasticClientSpi]) in a private[this] val on an object ⇒ latched at class-initialisation; reused by :71-77 IllegalStateException("No ElasticClientSpi implementation found") (:77) — loud, but misleading: the provider IS on the classpath, just not visible to the latched loader
licensing/src/main/scala/app/softnetwork/elastic/licensing/LicenseRefreshStrategyFactory.scala:112 single-arg ServiceLoader.load(classOf[LicenseManagerSpi]); resolved strategy CAS-latched JVM-wide at :129 falls through to new NopRefreshStrategy() (:124-128) — no warn, no exception. Licence refresh silently OFF for the lifetime of the JVM
core/src/main/scala/app/softnetwork/elastic/client/ExtensionRegistry.scala:37-39 single-arg ServiceLoader.load(classOf[ExtensionSpi]) inside a lazy val ⇒ latched at first force no JOIN / MV extension — issue #157's silent-wrong-answer mode: a JOIN query returns wrong results instead of erroring

ElasticClientFactory also registers sys.addShutdownHook at object init (:50-54) — a fourth thread whose context classloader is inherited from whoever touched the object first.

Proposed fix

Use the two-arg form at all three sites, anchoring on the SPI interface's own classloader:

ServiceLoader.load(classOf[ElasticClientSpi], classOf[ElasticClientSpi].getClassLoader)

(and the analogous change for LicenseManagerSpi and ExtensionSpi). This makes provider discovery independent of whichever thread happens to initialise the object first. The two silent sites should additionally log a WARN when the provider list is empty, so a future packaging mistake is not invisible.

Scope note

This is a follow-up to Story 20.1 — deliberately no elasticsql code change was made in that story (AD-3: one mechanism, one review surface; the drivers' swap covers them for the driver venues). The latches remain reachable by any other in-process embedder of softclient4es-core.

Refs: SOFTNETWORK-APP/softclient4es-jdbc#32, SOFTNETWORK-APP/softclient4es-arrow#166.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions