Make plugin manager lifecycle and inspection APIs thread-safe
This commit is contained in:
@@ -46,7 +46,8 @@ public final class GoPluginManager {
|
|||||||
private final ActionExecutor actions = new ActionExecutor();
|
private final ActionExecutor actions = new ActionExecutor();
|
||||||
private final Map<String, LoadedPlugin> plugins = new LinkedHashMap<>();
|
private final Map<String, LoadedPlugin> plugins = new LinkedHashMap<>();
|
||||||
private final List<PackageInspection> packageInspections = new ArrayList<>();
|
private final List<PackageInspection> packageInspections = new ArrayList<>();
|
||||||
private boolean serverRunning;
|
/** Lifecycle flag is read by the networking/admin thread as well as the server thread. */
|
||||||
|
private volatile boolean serverRunning;
|
||||||
|
|
||||||
public GoPluginManager(Logger logger) {
|
public GoPluginManager(Logger logger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -55,7 +56,18 @@ public final class GoPluginManager {
|
|||||||
this.dataDirectory = root.resolve("data");
|
this.dataDirectory = root.resolve("data");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void discover() {
|
public synchronized void discover() {
|
||||||
|
scanCandidates(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized ReloadResult rescan(MinecraftServer server) {
|
||||||
|
int before = this.plugins.size();
|
||||||
|
scanCandidates(server);
|
||||||
|
int added = this.plugins.size() - before;
|
||||||
|
return new ReloadResult(true, "Package check completed; new plugins: " + added);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanCandidates(MinecraftServer server) {
|
||||||
this.packageInspections.clear();
|
this.packageInspections.clear();
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(this.pluginDirectory);
|
Files.createDirectories(this.pluginDirectory);
|
||||||
@@ -66,6 +78,18 @@ public final class GoPluginManager {
|
|||||||
|
|
||||||
for (Path candidate : nativeCandidates()) {
|
for (Path candidate : nativeCandidates()) {
|
||||||
try {
|
try {
|
||||||
|
Path normalized = candidate.toAbsolutePath().normalize();
|
||||||
|
LoadedPlugin alreadyLoaded = this.plugins.values().stream()
|
||||||
|
.filter(plugin -> plugin.backend().origin().equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (alreadyLoaded != null) {
|
||||||
|
this.packageInspections.add(new PackageInspection(
|
||||||
|
normalized.toString(), true, alreadyLoaded.metadata().id(), null
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
LoadedPlugin plugin = new LoadedPlugin(new NativePluginBackend(candidate));
|
LoadedPlugin plugin = new LoadedPlugin(new NativePluginBackend(candidate));
|
||||||
LoadedPlugin duplicate = this.plugins.putIfAbsent(plugin.metadata().id(), plugin);
|
LoadedPlugin duplicate = this.plugins.putIfAbsent(plugin.metadata().id(), plugin);
|
||||||
if (duplicate != null) {
|
if (duplicate != null) {
|
||||||
@@ -85,6 +109,9 @@ public final class GoPluginManager {
|
|||||||
plugin.metadata().version(),
|
plugin.metadata().version(),
|
||||||
candidate.getFileName()
|
candidate.getFileName()
|
||||||
);
|
);
|
||||||
|
if (server != null && this.serverRunning) {
|
||||||
|
startPlugin(plugin, server);
|
||||||
|
}
|
||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
this.packageInspections.add(new PackageInspection(
|
this.packageInspections.add(new PackageInspection(
|
||||||
candidate.toAbsolutePath().normalize().toString(),
|
candidate.toAbsolutePath().normalize().toString(),
|
||||||
@@ -97,33 +124,33 @@ public final class GoPluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(MinecraftServer server) {
|
public synchronized void start(MinecraftServer server) {
|
||||||
this.serverRunning = true;
|
this.serverRunning = true;
|
||||||
for (LoadedPlugin plugin : this.plugins.values()) {
|
for (LoadedPlugin plugin : this.plugins.values()) {
|
||||||
startPlugin(plugin, server);
|
startPlugin(plugin, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick(MinecraftServer server) {
|
public synchronized void tick(MinecraftServer server) {
|
||||||
for (LoadedPlugin plugin : runningPlugins()) {
|
for (LoadedPlugin plugin : runningPlugins()) {
|
||||||
ServerSnapshot snapshot = this.snapshots.create(server, plugin.snapshotSubscription());
|
ServerSnapshot snapshot = this.snapshots.create(server, plugin.snapshotSubscription());
|
||||||
invoke(plugin, Protocol.Operation.TICK, snapshot, server);
|
invoke(plugin, Protocol.Operation.TICK, snapshot, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void chat(ChatEvent event, MinecraftServer server) {
|
public synchronized void chat(ChatEvent event, MinecraftServer server) {
|
||||||
for (LoadedPlugin plugin : runningPlugins()) {
|
for (LoadedPlugin plugin : runningPlugins()) {
|
||||||
invoke(plugin, Protocol.Operation.CHAT, event, server);
|
invoke(plugin, Protocol.Operation.CHAT, event, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void death(DeathEvent event, MinecraftServer server) {
|
public synchronized void death(DeathEvent event, MinecraftServer server) {
|
||||||
for (LoadedPlugin plugin : runningPlugins()) {
|
for (LoadedPlugin plugin : runningPlugins()) {
|
||||||
invoke(plugin, Protocol.Operation.DEATH, event, server);
|
invoke(plugin, Protocol.Operation.DEATH, event, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop(MinecraftServer server) {
|
public synchronized void stop(MinecraftServer server) {
|
||||||
for (LoadedPlugin plugin : runningPlugins()) {
|
for (LoadedPlugin plugin : runningPlugins()) {
|
||||||
try {
|
try {
|
||||||
PluginResponse response = plugin.invoke(
|
PluginResponse response = plugin.invoke(
|
||||||
@@ -141,10 +168,21 @@ public final class GoPluginManager {
|
|||||||
this.serverRunning = false;
|
this.serverRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<LoadedPlugin> plugins() {
|
public synchronized List<LoadedPlugin> plugins() {
|
||||||
return List.copyOf(this.plugins.values());
|
return List.copyOf(this.plugins.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the latest package validation results as an immutable snapshot. */
|
||||||
|
public synchronized List<PackageInspection> packageInspections() {
|
||||||
|
return List.copyOf(this.packageInspections);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a point-in-time copy of a plugin's retained logs, or an empty list when unknown. */
|
||||||
|
public synchronized List<PluginLog> pluginLogs(String pluginId) {
|
||||||
|
LoadedPlugin plugin = this.plugins.get(pluginId);
|
||||||
|
return plugin == null ? List.of() : plugin.logs();
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized BridgeManagementSnapshot managementSnapshot(boolean canReload, String message) {
|
public synchronized BridgeManagementSnapshot managementSnapshot(boolean canReload, String message) {
|
||||||
List<ManagedPluginSnapshot> pluginSnapshots = this.plugins.values().stream()
|
List<ManagedPluginSnapshot> pluginSnapshots = this.plugins.values().stream()
|
||||||
.map(plugin -> new ManagedPluginSnapshot(
|
.map(plugin -> new ManagedPluginSnapshot(
|
||||||
|
|||||||
Reference in New Issue
Block a user