Defer runtime injection

- Add RuntimeInjection so the Java agent attach and the server-code injection run once, on demand, instead of during every enable
- Install the injection when a world Iris created is about to be built and when Bukkit asks for the generator of a world configured outside Iris
- Lock the runtime with the same reason a boot-time injection failure used to raise when the deferred install fails
- Add general.eagerRuntimeInjection to keep boot-time injection and its startup verification
- Re-read the startup lock after a deferred install so a failed install refuses the world instead of generating it
- Move the stale cache/temp sweep off the boot thread and make every pack import wait it out
- Move the default-pack presence warning off the boot thread; it walks the whole pack tree to pick a warning
- Build the wand and dust templates on the first tick instead of during service enable
- Report enable duration, per-phase timings and the three slowest services on every boot
- Drop the wand and dust templates on service disable
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-08 04:45:55 -04:00
1 parent 6c6dba0232
commit 486c6eeae9
11 files changed
+426 -35

No files matched your search

@@ -0,0 +1,73 @@
package art.arcane.iris.core.runtime;
import art.arcane.iris.core.IrisStartupValidation;
import art.arcane.iris.testsupport.BukkitTestServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* Attaching the agent and retransforming server classes costs about a second of every boot, and a server
* that never loads an Iris world never needs either. Deferring it only stays safe while a deferred failure
* locks the runtime exactly like a boot-time one did, so the world it was about to generate is refused
* rather than handed to uninstrumented server code.
*/
public class RuntimeInjectionTest {
@Before
public void resetInjection() {
BukkitTestServer.install();
RuntimeInjection.reset();
}
@After
public void clearValidation() {
RuntimeInjection.reset();
IrisStartupValidation.disable();
}
@Test
public void anInstallWithoutAUsableNmsBindingIsRefusedAndNamesTheReason() {
RuntimeInjection.Outcome outcome = RuntimeInjection.install();
assertFalse(outcome.installed());
assertEquals(RuntimeInjection.Failure.NO_BINDING, outcome.failure());
assertTrue(outcome.lockReason(), outcome.lockReason().contains("NMS runtime"));
}
@Test
public void theOutcomeIsResolvedOnceUntilReset() {
RuntimeInjection.Outcome first = RuntimeInjection.install();
assertSame(first, RuntimeInjection.install());
RuntimeInjection.reset();
assertFalse(RuntimeInjection.install() == first);
}
@Test
public void aDeferredInstallFailureLocksTheRuntimeWithItsReason() {
IrisStartupValidation.begin();
IrisStartupValidation.markDatapacksReady();
IrisStartupValidation.markPacksReady();
IrisStartupValidation.markRuntimeReady();
assertTrue(IrisStartupValidation.isReady());
RuntimeInjection.installIfDeferred();
assertFalse(IrisStartupValidation.isReady());
assertEquals(RuntimeInjection.install().lockReason(),
IrisStartupValidation.denialReason().orElseThrow());
}
@Test
public void anUninstalledRuntimeReportsItself() {
RuntimeInjection.install();
assertFalse(RuntimeInjection.isInstalled());
}
}
@@ -0,0 +1,79 @@
package art.arcane.iris.core.safeguard;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.safeguard.task.CheckResult;
import art.arcane.iris.core.safeguard.task.Diagnostic;
import art.arcane.iris.core.safeguard.task.Task;
import art.arcane.iris.core.safeguard.task.Tasks;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.testsupport.BukkitTestServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
/**
* With injection deferred the injection check has nothing to verify yet, so it must say so and stay Stable
* rather than reporting a Danger the server has not actually hit. Turning the setting on brings the boot
* verification back.
*/
public class TasksInjectionDeferralTest {
private IrisPlatform previousPlatform;
private IrisSettings previousSettings;
@Before
public void installServerAndSettings() {
BukkitTestServer.install();
previousPlatform = IrisPlatforms.isBound() ? IrisPlatforms.get() : null;
IrisPlatforms.unbind();
IrisPlatforms.bind(mock(IrisPlatform.class));
previousSettings = IrisSettings.settings;
IrisSettings.settings = new IrisSettings();
}
@After
public void restore() {
IrisSettings.settings = previousSettings;
IrisPlatforms.unbind();
if (previousPlatform != null) {
IrisPlatforms.bind(previousPlatform);
}
}
@Test
public void injectionIsDeferredByDefaultAndSaysSo() {
assertTrue("deferred injection is the default", !IrisSettings.settings.getGeneral().isEagerRuntimeInjection());
CheckResult result = injection().run();
assertEquals(Mode.STABLE, result.mode());
List<String> lines = result.diagnostics().stream().map(Diagnostic::getMessage).toList();
assertTrue(lines.toString(), lines.contains("Runtime Injection"));
assertTrue(lines.toString(), lines.stream().anyMatch(line -> line.contains("general.eagerRuntimeInjection")));
}
@Test
public void theEagerSettingBringsBackBootTimeVerification() {
IrisSettings.settings.getGeneral().setEagerRuntimeInjection(true);
CheckResult result = injection().run();
assertEquals(Mode.UNSTABLE, result.mode());
assertTrue(result.lockReason(), result.lockReason().contains("NMS runtime"));
}
private static Task injection() {
for (Task task : Tasks.getTasks()) {
if ("injection".equals(task.getId())) {
return task;
}
}
throw new IllegalStateException("The injection check is missing from the safeguard task list");
}
}