Skip to content

fix(core): align libp2p endpoint runtime constructor#62

Merged
robinbraemer merged 3 commits into
mainfrom
fm/connect-java-java26-libp2p-init-fix
Jul 24, 2026
Merged

fix(core): align libp2p endpoint runtime constructor#62
robinbraemer merged 3 commits into
mainfrom
fm/connect-java-java26-libp2p-init-fix

Conversation

@robinbraemer

Copy link
Copy Markdown
Member

Intent

Fix Connect (connect-java) failing to initialize its libp2p endpoint runtime, reported by a user on Purpur 26.1.2 / Java 26.0.1 / Connect Spigot v0.12.1: the plugin logs 'Failed to initialize Connect libp2p endpoint runtime' with java.lang.NoSuchMethodException on Libp2pEndpointRuntime., the endpoint never starts, and every join fails with 'No available Browser Hub'. Root cause (proven, not the reported Java-26 theory): a JDK-INDEPENDENT constructor arity mismatch. PR #57 (trusted Bedrock identity sessions) added BedrockIdentityReadiness and BedrockAdmissionCoordinator to Libp2pEndpointRuntime's constructor (now 8/9-arg) but left Libp2pEndpoint's reflective getDeclaredConstructor(...) at the old 7-arg signature, so the reflective lookup across the isolated child-first classloader matches nothing and throws NoSuchMethodException, causing runtime=null. Verified by reproduction that this fails identically on Java 21 AND Java 26, and that jvm-libp2p/kotlin classes load fine on Java 26 — so it is NOT an upstream jvm-libp2p incompatibility and NOT fixed by a dependency bump, Java downgrade, or Gate-standalone workaround (all explicitly out of scope). Fix: add both Bedrock deps to Libp2pEndpoint's @Inject constructor (both are already provided in the same plugin injector — the co-located eager singleton WatcherRegister injects them today, so no startup regression) and extend the reflective lookup + newInstance to the real 9-arg constructor, passing the real BedrockAdmissionCoordinator (not the 8-arg null-coordinator overload). Added regression test Libp2pEndpointRuntimeInitTest that constructs the wrapper and asserts the runtime initializes (non-null); it was proven to fail with runtime==null under the pre-fix 7-arg lookup via a single-condition counterfactual and to pass after. Documented the reflective wrapper<->isolated-runtime boundary contract in AGENTS.md (a CLAUDE.md symlink was created by the project helper). Validated: real fixed wrapper yields a non-null Libp2pEndpointRuntime on both Java 21 and Java 26 with no error logged; full core suite of 249 tests passes; all platform modules (api/core/spigot/bungee/velocity) compile; Java 11 bytecode target preserved. This is a fix: commit (patch).

What Changed

  • Updated Libp2pEndpoint to inject Bedrock dependencies and resolve/invoke the isolated runtime’s nine-argument constructor.
  • Added a regression test covering runtime initialization across the classloader boundary and coordinator propagation.
  • Documented the reflective libp2p runtime boundary in AGENTS.md and linked CLAUDE.md to it.

Risk Assessment

✅ Low: The updated change is well-bounded: production wiring matches the nine-argument runtime constructor, and the regression test now verifies the real admission coordinator crosses the isolated loader boundary and is closed afterward.

Testing

The focused runtime-init regression passed on Java 21 and Java 26, with the Java 26 Gradle test worker explicitly confirmed. Platform modules compiled successfully; the boundary guard passed; both relevant classes retained Java 11 bytecode (major 55) and matching 9-argument constructor descriptors. Gradle 8.5 itself cannot start directly on Java 26 due its embedded Kotlin parser, so Gradle ran on Java 21 while only the test worker used Java 26. No full suite or linters were run, and transient worktree build outputs were removed.

Evidence: Java 26 runtime initialization
Java 26 test worker used `/Users/robin/.local/share/mise/installs/java/temurin-26.0.1+8/bin/java`; focused runtime-init test completed with `BUILD SUCCESSFUL`.
Evidence: Java 21 test report
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="x-ua-compatible" content="IE=edge"/>
<title>Test results - Libp2pEndpointRuntimeInitTest</title>
<link href="../css/base-style.css" rel="stylesheet" type="text/css"/>
<link href="../css/style.css" rel="stylesheet" type="text/css"/>
<script src="../js/report.js" type="text/javascript"></script>
</head>
<body>
<div id="content">
<h1>Libp2pEndpointRuntimeInitTest</h1>
<div class="breadcrumbs">
<a href="../index.html">all</a> &gt; 
<a href="../packages/com.minekube.connect.tunnel.p2p.html">com.minekube.connect.tunnel.p2p</a> &gt; Libp2pEndpointRuntimeInitTest</div>
<div id="summary">
<table>
<tr>
<td>
<div class="summaryGroup">
<table>
<tr>
<td>
<div class="infoBox" id="tests">
<div class="counter">1</div>
<p>tests</p>
</div>
</td>
<td>
<div class="infoBox" id="failures">
<div class="counter">0</div>
<p>failures</p>
</div>
</td>
<td>
<div class="infoBox" id="ignored">
<div class="counter">0</div>
<p>ignored</p>
</div>
</td>
<td>
<div class="infoBox" id="duration">
<div class="counter">0.154s</div>
<p>duration</p>
</div>
</td>
</tr>
</table>
</div>
</td>
<td>
<div class="infoBox success" id="successRate">
<div class="percent">100%</div>
<p>successful</p>
</div>
</td>
</tr>
</table>
</div>
<div id="tabs">
<ul class="tabLinks">
<li>
<a href="#tab0">Tests</a>
</li>
</ul>
<div id="tab0" class="tab">
<h2>Tests</h2>
<table>
<thead>
<tr>
<th>Test</th>
<th>Duration</th>
<th>Result</th>
</tr>
</thead>
<tr>
<td class="success">initializesRuntimeAcrossIsolatedLoaderBoundary(Path)</td>
<td class="success">0.154s</td>
<td class="success">passed</td>
</tr>
</table>
</div>
</div>
<div id="footer">
<p>
<div>
<label class="hidden" id="label-for-line-wrapping-toggle" for="line-wrapping-toggle">Wrap lines
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
</label>
</div>Generated by 
<a href="http://www.gradle.org">Gradle 8.5</a> at Jul 24, 2026, 10:28:30 AM</p>
</div>
</div>
</body>
</html>
Evidence: Java 26 test report
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="x-ua-compatible" content="IE=edge"/>
<title>Test results - Libp2pEndpointRuntimeInitTest</title>
<link href="../css/base-style.css" rel="stylesheet" type="text/css"/>
<link href="../css/style.css" rel="stylesheet" type="text/css"/>
<script src="../js/report.js" type="text/javascript"></script>
</head>
<body>
<div id="content">
<h1>Libp2pEndpointRuntimeInitTest</h1>
<div class="breadcrumbs">
<a href="../index.html">all</a> &gt; 
<a href="../packages/com.minekube.connect.tunnel.p2p.html">com.minekube.connect.tunnel.p2p</a> &gt; Libp2pEndpointRuntimeInitTest</div>
<div id="summary">
<table>
<tr>
<td>
<div class="summaryGroup">
<table>
<tr>
<td>
<div class="infoBox" id="tests">
<div class="counter">1</div>
<p>tests</p>
</div>
</td>
<td>
<div class="infoBox" id="failures">
<div class="counter">0</div>
<p>failures</p>
</div>
</td>
<td>
<div class="infoBox" id="ignored">
<div class="counter">0</div>
<p>ignored</p>
</div>
</td>
<td>
<div class="infoBox" id="duration">
<div class="counter">0.166s</div>
<p>duration</p>
</div>
</td>
</tr>
</table>
</div>
</td>
<td>
<div class="infoBox success" id="successRate">
<div class="percent">100%</div>
<p>successful</p>
</div>
</td>
</tr>
</table>
</div>
<div id="tabs">
<ul class="tabLinks">
<li>
<a href="#tab0">Tests</a>
</li>
</ul>
<div id="tab0" class="tab">
<h2>Tests</h2>
<table>
<thead>
<tr>
<th>Test</th>
<th>Duration</th>
<th>Result</th>
</tr>
</thead>
<tr>
<td class="success">initializesRuntimeAcrossIsolatedLoaderBoundary(Path)</td>
<td class="success">0.166s</td>
<td class="success">passed</td>
</tr>
</table>
</div>
</div>
<div id="footer">
<p>
<div>
<label class="hidden" id="label-for-line-wrapping-toggle" for="line-wrapping-toggle">Wrap lines
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
</label>
</div>Generated by 
<a href="http://www.gradle.org">Gradle 8.5</a> at Jul 24, 2026, 10:31:59 AM</p>
</div>
</div>
</body>
</html>
Evidence: Java 11 bytecode and constructor evidence
Libp2pEndpoint.class
  major version: 55
    descriptor: (Ljava/nio/file/Path;Lcom/minekube/connect/config/ConnectConfig;Ljava/lang/String;Lcom/minekube/connect/platform/util/PlatformUtils;Lcom/minekube/connect/api/logger/ConnectLogger;Lcom/minekube/connect/api/inject/PlatformInjector;Lcom/minekube/connect/api/SimpleConnectApi;Lcom/minekube/connect/bedrock/BedrockIdentityReadiness;Lcom/minekube/connect/bedrock/BedrockAdmissionCoordinator;)V
Libp2pEndpointRuntime.class
  major version: 55
    descriptor: (Ljava/nio/file/Path;Lcom/minekube/connect/config/ConnectConfig;Ljava/lang/String;Lcom/minekube/connect/platform/util/PlatformUtils;Lcom/minekube/connect/api/logger/ConnectLogger;Lcom/minekube/connect/api/inject/PlatformInjector;Lcom/minekube/connect/api/SimpleConnectApi;Lcom/minekube/connect/bedrock/BedrockIdentityReadiness;Lcom/minekube/connect/bedrock/BedrockAdmissionCoordinator;)V
    descriptor: (Ljava/nio/file/Path;Lcom/minekube/connect/config/ConnectConfig;Ljava/lang/String;Lcom/minekube/connect/platform/util/PlatformUtils;Lcom/minekube/connect/api/logger/ConnectLogger;Lcom/minekube/connect/api/inject/PlatformInjector;Lcom/minekube/connect/api/SimpleConnectApi;Lcom/minekube/connect/bedrock/BedrockIdentityReadiness;)V

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ⚠️ core/src/test/java/com/minekube/connect/tunnel/p2p/Libp2pEndpointRuntimeInitTest.java:71 - The test passes null for both Bedrock dependencies and only checks runtime != null. Because the runtime still has an 8-argument overload with a null coordinator, a regression to that overload would pass while bypassing Bedrock admission for libp2p sessions. Assert that the nine-argument constructor stores the real coordinator.

🔧 Fix: Strengthen libp2p coordinator wiring regression test
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • ./gradlew :core:test --tests com.minekube.connect.tunnel.p2p.Libp2pEndpointRuntimeInitTest on Java 21
  • Focused test worker on Java 26 via temporary Gradle init configuration
  • ./gradlew :core:test --tests com.minekube.connect.tunnel.p2p.Libp2pRuntimeBoundaryTest
  • ./gradlew :api:compileJava :core:compileJava :spigot:compileJava :bungee:compileJava :velocity:compileJava
  • javap -verbose on Libp2pEndpoint and Libp2pEndpointRuntime classes
  • Final git status --short --branch cleanup check
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…deps

Libp2pEndpoint loads the isolated Libp2pEndpointRuntime through
Libp2pRuntimeLoader.classLoader() and resolves its constructor reflectively
by exact signature. PR #57 (trusted Bedrock identity sessions) added
BedrockIdentityReadiness and BedrockAdmissionCoordinator to the runtime
constructor (now 8/9-arg) but left the wrapper's getDeclaredConstructor(...)
lookup at the old 7-arg signature.

The lookup therefore matches no constructor and throws NoSuchMethodException,
so the wrapper sets runtime=null and logs "Failed to initialize Connect
libp2p endpoint runtime". The endpoint never starts and every join fails
downstream with "No available Browser Hub".

This drift is JDK-independent: it reproduces identically on Java 21 and
Java 26 (and jvm-libp2p/kotlin classes load fine on Java 26), so it is not
an upstream jvm-libp2p incompatibility despite surfacing when a user
upgraded to Java 26.

Wire both Bedrock dependencies through Libp2pEndpoint's @Inject constructor
(both are already provided in the same injector — the co-located eager
singleton WatcherRegister injects them today) and extend the reflective
lookup and newInstance to the 9-arg constructor.

Add Libp2pEndpointRuntimeInitTest, which constructs the wrapper and asserts
the runtime initializes; it fails (runtime==null) with the pre-fix 7-arg
lookup and passes after. Document the reflective boundary contract in
AGENTS.md.
@robinbraemer
robinbraemer merged commit b617725 into main Jul 24, 2026
1 check passed
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.

1 participant