Skip to content

Race condition in MajordomoRestPlugin.registerEndPoint() causes intermittent CI failures in MajordomoRestPluginTests #275

Description

@t0susnik

Summary

MajordomoRestPluginTests fails intermittently with either:

java.util.ConcurrentModificationException
	at io.javalin.http.PathMatcher.add(PathMatcher.kt:26)
	at io.opencmw.server.rest.MajordomoRestPlugin.lambda$registerEndPoint$10(MajordomoRestPlugin.java:361)

or

java.lang.IllegalArgumentException: Handler with type='POST' and path='/PrimaryBroker/mmi.openapi' already exists.
	at io.javalin.http.PathMatcher.add(PathMatcher.kt:27)

Observed on CI in an unrelated PR (#273) and reproduced locally, reliably within ~10
back-to-back runs of MajordomoRestPluginTests.

Root cause

MajordomoRestPlugin.start() registers endpoints from two independent threads: the
synchronous initial service enumeration, and the async getServiceSubscriptionTask()
listener reacting to the same broker registrations as they happen. Both call
registerEndPoint(), which de-duplicates repeated registration of the same endpoint via
registeredEndpoints.computeIfAbsent(...) — but that guard does nothing to stop two
different endpoints from being registered concurrently. Both paths mutate the same Javalin
route table (RestServer.getInstance(), a JVM-wide singleton, backed by a plain,
non-thread-safe ArrayList in PathMatcher), so a burst of near-simultaneous service
announcements (as happens during startup) can trigger a genuine data race on that shared,
unsynchronized list.

The failed registration then leaves providedServices without an entry for the affected
service, which can surface downstream as an unrelated-looking NullPointerException on
MajordomoBroker.DnsServiceItem.getUri().

Fix

Will submit a PR adding a dedicated lock around the Javalin route-mutation call, scoped
static since the underlying Javalin instance is shared across every MajordomoRestPlugin
instance in the JVM, not just one instance's own threads.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions