Anvil
Building blocksEnvironmentsActions

Restarting processes

Replace one process while preserving its workspace and verify an explicit player reconnection.

Restart a process when a test needs to exercise initialization again using files already written to its workspace. processes.restart(name) replaces that one server or proxy and waits for readiness. Other processes continue running.

Start and stop individual components

For an existing ScenarioEngine engine from AnvilLauncher and a wired AnvilScenario network, prepare the environment without starting any JVMs, then choose its initial components:

import me.whereareiam.anvil.api.scenario.ScenarioContext;

try (ScenarioContext context = engine.prepare(network)) {
	context.processes().start("lobby");
	context.processes().start("proxy");

	// Starts the remaining components and runs the scenario setup hook once.
	context.start();

	context.processes().stop("lobby");
	var replacement = context.processes().start("lobby");
	context.start(); // Restores any remaining stopped components; setup does not repeat.
}

Preparation validates and provisions the complete topology, preserving its ports, routes, and forwarding configuration. Starting a component starts only that process, so a proxy may have configured routes to backends that are not running yet. Collections contain processes that have had a generation; looking up a known but never-started process reports that it has not started. Start is idempotent for a ready process. Stop retains its address, installed assets, and workspace; starting it again creates a fresh generation and reconnects its platform agent. Closing the context also releases prepared components that were never started.

ScenarioContext owns the topology from preparation through startup and finalization. engine.start(network) performs preparation and complete startup before returning that same kind of context. Use it for the ordinary test path; use engine.prepare(network) when your application controls which components start first.

A failed startup finalizes the prepared context unsuccessfully. If your own actions or assertions fail after startup, pass that outcome through context.finish(false); default close() reports normal caller completion. Cleanup attempts every owned resource and preserves earlier lifecycle failures.

Verify a proxy restart

Use the proxy environment, with proxy as the entrypoint and lobby as its backend. Enable rapid reconnections in the Velocity or BungeeCord declaration when its throttle would delay this test. The Session and Players capabilities must be installed.

Place this helper at src/anvil/java/com/example/test/ProxyRestartChecks.java. Call ProxyRestartChecks.verify(anvil) from a test method in the same package:

package com.example.test;

import me.whereareiam.anvil.api.scenario.ScenarioContext;
import me.whereareiam.anvil.api.type.ProcessState;
import me.whereareiam.anvil.capability.players.PlayerWatch;
import me.whereareiam.anvil.capability.players.Players;
import me.whereareiam.anvil.capability.session.Session;

import java.time.Duration;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;

public final class ProxyRestartChecks {
	public static void verify(ScenarioContext anvil) {
		var processes = anvil.processes();
		var original = processes.proxy("proxy");
		var lobby = processes.server("lobby");
		var alice = anvil.players().create("RestartAlice");
		var session = alice.capability(Session.class);
		Players proxyPlayers = original.capability(Players.class);
		PlayerWatch arrivals = lobby.capability(Players.class).watch();
		PlayerWatch moves = proxyPlayers.watch();

		session.connect();
		session.connected(Duration.ofSeconds(30));
		arrivals.joined(alice);
		moves.joined(alice, "lobby");

		var replacement = processes.restart("proxy");
		assertNotSame(original, replacement);
		assertEquals(ProcessState.STOPPED, original.state());
		assertEquals(ProcessState.READY, replacement.state());
		assertEquals(original.address(), replacement.address());
		assertEquals(original.workDirectory(), replacement.workDirectory());
		assertSame(lobby, processes.server("lobby"));

		session.disconnected();
		session.rejoin();
		session.connected(Duration.ofSeconds(30));
		arrivals.joined(alice);
		moves.joined(alice, "lobby");
		assertSame(proxyPlayers, replacement.capability(Players.class));
		assertEquals(Optional.of("lobby"), proxyPlayers.backend(alice));
	}
}

Run the containing test with ./gradlew anvilTest --tests 'your.package.YourTest'. This checks replacement of the proxy, preservation of the lobby, and reconnection of the same registered player to that lobby. The proxy's Players is the same instance before and after the restart, so the watch moves created before it continues: the restart ended the visits the earlier generation reported, and the second moves.joined(alice, "lobby") waits for the replacement proxy to connect her to lobby. After that wait, backend(alice) names the same backend.

To test persistence, write application state before the restart and assert that state after rejoining. A successful reconnection alone does not prove that plugin data survived.

What remains after a restart

ResourceBehavior
WorkspaceSame directory and data already written to disk
Game listenerSame address and port
Other processesContinue running
Installed assetsRemain in place; initial asset installation and cache restoration do not repeat
Platform configurationReapplied before launch
Agent connectionReestablished with fresh credentials
Process capability instancesRetained for the logical process; agent-backed operations may be unavailable during replacement
Original process handleRemains attached to the stopped process
Simulated playerRemains registered; its connection needs an explicit reconnect

Use the returned handle or look up the process again for further console or state observations. Restarting a backend can disconnect or reroute a player according to the proxy's behavior; assert that outcome before continuing the journey.

Handle restart failures

Let unexpected restart failures fail the test and inspect the affected process's console log. A ProcessException exposes its name through getProcessName(). A failed restart marks the scenario lifecycle unsuccessful even if your test catches the exception; success-only caches are not finalized as a successful run.

For newly installed assets or cache restoration, start a new scenario. For data shared across separate runs, declare a workspace policy.

On this page