Skip to content

Commit 9cccb94

Browse files
minekube-ai-engineer[bot]Minekube AI Engineer
andauthored
fix(core): share Bedrock admission coordinator across injectors (#151)
Co-authored-by: Minekube AI Engineer <1535738075139801220+minekube-ai@users.noreply.github.com>
1 parent 25edce3 commit 9cccb94

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

core/src/main/java/com/minekube/connect/module/CommonModule.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.minekube.connect.api.inject.PlatformInjector;
4040
import com.minekube.connect.api.logger.ConnectLogger;
4141
import com.minekube.connect.api.packet.PacketHandlers;
42+
import com.minekube.connect.bedrock.BedrockAdmissionCoordinator;
4243
import com.minekube.connect.bedrock.BedrockIdentityKeyProvider;
4344
import com.minekube.connect.bedrock.BedrockIdentityReadiness;
4445
import com.minekube.connect.bedrock.BedrockPrincipalReadiness;
@@ -81,6 +82,13 @@ protected void configure() {
8182

8283
bind(PacketHandlers.class).to(PacketHandlersImpl.class);
8384
bind(PacketHandlersImpl.class).asEagerSingleton();
85+
86+
// This binding must be explicit on the common parent injector. A just-in-time singleton
87+
// requested from later config/post-initialize children is scoped to that child, which can
88+
// make WatchClient mint an admission on one coordinator while WatcherRegister stages it on
89+
// another. Explicit parent bindings are inherited by every child injector.
90+
bind(BedrockAdmissionCoordinator.class).in(Singleton.class);
91+
8492
Multibinder<TunnelClientTransport> transports =
8593
Multibinder.newSetBinder(binder(), TunnelClientTransport.class);
8694
transports.addBinding().to(WebSocketTunnelTransport.class);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.minekube.connect;
2+
3+
import static org.junit.jupiter.api.Assertions.assertSame;
4+
import static org.mockito.Mockito.mock;
5+
6+
import com.google.inject.AbstractModule;
7+
import com.google.inject.Guice;
8+
import com.google.inject.Injector;
9+
import com.google.inject.Module;
10+
import com.google.inject.util.Modules;
11+
import com.minekube.connect.bedrock.BedrockAdmissionCoordinator;
12+
import com.minekube.connect.config.ConfigHolder;
13+
import com.minekube.connect.config.ProxyConnectConfig;
14+
import com.minekube.connect.module.ConfigLoadedModule;
15+
import com.minekube.connect.module.PostInitializeModule;
16+
import com.minekube.connect.module.ProxyCommonModule;
17+
import com.minekube.connect.module.VelocityPlatformModule;
18+
import com.minekube.connect.module.WatcherModule;
19+
import com.minekube.connect.register.WatchHealthServer;
20+
import com.minekube.connect.register.WatcherRegister;
21+
import com.minekube.connect.watch.WatchClient;
22+
import com.velocitypowered.api.event.EventManager;
23+
import com.velocitypowered.api.plugin.annotation.DataDirectory;
24+
import com.velocitypowered.api.proxy.ProxyServer;
25+
import java.lang.reflect.Field;
26+
import java.nio.file.Path;
27+
import java.util.Collections;
28+
import java.util.IdentityHashMap;
29+
import java.util.Set;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.io.TempDir;
32+
import org.slf4j.Logger;
33+
34+
/** Regression guard for one Bedrock admission coordinator across the Velocity injector chain. */
35+
class VelocityBedrockAdmissionCoordinatorScopeTest {
36+
@TempDir Path tempDir;
37+
38+
@Test
39+
void watcherMintAndStageShareThePlatformCoordinatorAcrossChildInjectors() throws Exception {
40+
Injector velocityRoot = Guice.createInjector(new AbstractModule() {
41+
@Override
42+
protected void configure() {
43+
bind(ProxyServer.class).toInstance(mock(ProxyServer.class));
44+
bind(EventManager.class).toInstance(mock(EventManager.class));
45+
bind(Logger.class).toInstance(mock(Logger.class));
46+
bind(Path.class).annotatedWith(DataDirectory.class).toInstance(tempDir);
47+
}
48+
});
49+
50+
// VelocityPlugin constructor: common/platform child, then ConnectPlatform.init()'s config
51+
// child, then ConnectPlatform.enable()'s post-initialize child.
52+
Injector platform = velocityRoot.createChildInjector(
53+
new ProxyCommonModule(tempDir),
54+
new VelocityPlatformModule(velocityRoot));
55+
BedrockAdmissionCoordinator platformCoordinator =
56+
platform.getInstance(BedrockAdmissionCoordinator.class);
57+
58+
ProxyConnectConfig config = new ProxyConnectConfig();
59+
platform.getInstance(ConfigHolder.class).set(config);
60+
Injector configured = platform.createChildInjector(new ConfigLoadedModule(config));
61+
62+
Module watcherWithoutStartupSideEffects = Modules.override(new WatcherModule())
63+
.with(new AbstractModule() {
64+
@Override
65+
protected void configure() {
66+
bind(WatcherRegister.class)
67+
.to(NoStartWatcherRegister.class)
68+
.asEagerSingleton();
69+
bind(WatchHealthServer.class).toInstance(mock(WatchHealthServer.class));
70+
}
71+
});
72+
Injector enabled = configured.createChildInjector(
73+
new PostInitializeModule(new Module[] {watcherWithoutStartupSideEffects}));
74+
75+
WatcherRegister register = enabled.getInstance(WatcherRegister.class);
76+
WatchClient watchClient =
77+
(WatchClient) field(register, WatcherRegister.class, "watchClient");
78+
BedrockAdmissionCoordinator mintCoordinator =
79+
(BedrockAdmissionCoordinator)
80+
field(watchClient, WatchClient.class, "admissionCoordinator");
81+
BedrockAdmissionCoordinator stageCoordinator =
82+
(BedrockAdmissionCoordinator)
83+
field(register, WatcherRegister.class, "admissionCoordinator");
84+
85+
Set<BedrockAdmissionCoordinator> coordinators =
86+
Collections.newSetFromMap(new IdentityHashMap<>());
87+
Collections.addAll(
88+
coordinators, platformCoordinator, mintCoordinator, stageCoordinator);
89+
try {
90+
assertSame(mintCoordinator, stageCoordinator,
91+
"WatchClient mint and WatcherRegister/LocalSession stage must share a coordinator");
92+
assertSame(platformCoordinator, mintCoordinator,
93+
"WatchClient must mint admissions on the platform coordinator");
94+
assertSame(platformCoordinator, stageCoordinator,
95+
"WatcherRegister/LocalSession must stage admissions on the platform coordinator");
96+
} finally {
97+
coordinators.forEach(BedrockAdmissionCoordinator::close);
98+
}
99+
}
100+
101+
private static Object field(Object target, Class<?> declaringClass, String name)
102+
throws Exception {
103+
Field field = declaringClass.getDeclaredField(name);
104+
field.setAccessible(true);
105+
return field.get(target);
106+
}
107+
108+
/** Keeps the real WatcherRegister member-injection graph without opening a WebSocket. */
109+
public static final class NoStartWatcherRegister extends WatcherRegister {
110+
@Override
111+
public synchronized void start() {
112+
// Deliberately no-op: Guice still injects WatchClient and admissionCoordinator fields.
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)