Rename load/unload to mount/unmount, add suspend for temporary unload (e.g. containing chunk unload).

This commit is contained in:
Florian Nücke
2021-07-19 23:38:29 +02:00
parent dfd9de3aa2
commit ab4d0ad3c3
17 changed files with 138 additions and 96 deletions

View File

@@ -15,7 +15,7 @@ import li.cil.sedna.api.device.MemoryMappedDevice;
* in the guest system.
* <p>
* To listen to lifecycle events of the VM and the device, register to the event
* bus provided via {@link VMContext#getEventBus()} in {@link #load(VMContext)}.
* bus provided via {@link VMContext#getEventBus()} in {@link #mount(VMContext)}.
*
* @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider
* @see li.cil.oc2.api.bus.device.provider.ItemDeviceProvider
@@ -35,7 +35,7 @@ public interface VMDevice extends Device {
* @param context the virtual machine context.
* @return {@code true} if the device was loaded successfully; {@code false} otherwise.
*/
VMDeviceLoadResult load(VMContext context);
VMDeviceLoadResult mount(VMContext context);
/**
* Called when the device is removed from the context it was loaded with.
@@ -43,7 +43,17 @@ public interface VMDevice extends Device {
* This can happen because the VM was stopped or the device was removed from
* the device bus that connected it to the VM, for example.
* <p>
* Intended for releasing resources acquired in {@link #load(VMContext)}.
* Intended for releasing resources acquired in {@link #mount(VMContext)}.
*/
void unload();
void unmount();
/**
* Called when the device is suspended.
* <p>
* This can happen when the world area containing the context the device was loaded in is unloaded,
* e.g. due to player moving too far away from the area.
* <p>
* Intended for soft-releasing resources acquired in {@link #mount(VMContext)}.
*/
void suspend();
}

View File

@@ -6,7 +6,7 @@ import net.minecraft.util.text.ITextComponent;
import javax.annotation.Nullable;
/**
* {@link VMDevice}s may signal the result of their {@link VMDevice#load(VMContext)} operations.
* {@link VMDevice}s may signal the result of their {@link VMDevice#mount(VMContext)} operations.
*/
public final class VMDeviceLoadResult {
/**

View File

@@ -7,7 +7,7 @@ import java.util.OptionalInt;
/**
* Allows reserving interrupts on the primary interrupt controller of a virtual machine
* during a {@link VMDevice#load(VMContext)} call.
* during a {@link VMDevice#mount(VMContext)} call.
* <p>
* Allocated interrupts should be persisted and used in {@link #claimInterrupt(int)}
* when restoring from a saved state to ensure correct behaviour of the loaded virtual

View File

@@ -7,7 +7,7 @@ import java.util.OptionalLong;
/**
* Allows adding {@link MemoryMappedDevice}s to the memory map of a virtual machine
* during a {@link VMDevice#load(VMContext)} call.
* during a {@link VMDevice#mount(VMContext)} call.
* <p>
* Allocated addresses should be persisted and used in {@link #claimMemoryRange(long, MemoryMappedDevice)}
* when restoring from a saved state to ensure correct behaviour of the loaded virtual

View File

@@ -40,7 +40,7 @@ public interface VMContext {
/**
* Allows adding {@link MemoryMappedDevice}s to the VM's {@link MemoryMap}.
* <p>
* {@link MemoryMappedDevice}s can only be added inside {@link VMDevice#load(VMContext)}.
* {@link MemoryMappedDevice}s can only be added inside {@link VMDevice#mount(VMContext)}.
* Trying to add devices after that method has returned will result in an exception.
* <p>
* Added devices will be automatically removed when the {@link VMDevice} that added it
@@ -54,7 +54,7 @@ public interface VMContext {
/**
* Allows claiming interrupts for use with the VM's {@link InterruptController}.
* <p>
* Interrupts can only be claimed inside {@link VMDevice#load(VMContext)}.
* Interrupts can only be claimed inside {@link VMDevice#mount(VMContext)}.
* Trying to claim interrupts after that method has returned will result in an exception.
* <p>
* Claimed interrupts will automatically be released when the {@link VMDevice} that
@@ -73,7 +73,7 @@ public interface VMContext {
* running VMs.
* <p>
* Devices failing to reserve the memory they would use should fail their
* {@link VMDevice#load(VMContext)}.
* {@link VMDevice#mount(VMContext)}.
* <p>
* Memory will automatically be released when the {@link VMDevice} that claimed
* it is unloaded, e.g. because it is removed from the {@link DeviceBus} or the
@@ -97,7 +97,7 @@ public interface VMContext {
/**
* Waits for the executor thread of the virtual machine to finish running.
* <p>
* Events subscribers can only be registered inside {@link VMDevice#load(VMContext)}.
* Events subscribers can only be registered inside {@link VMDevice#mount(VMContext)}.
* Trying to register subscribers after that method has returned will result in an
* exception.
* <p>

View File

@@ -6,7 +6,7 @@ import li.cil.oc2.api.bus.device.vm.context.VMContext;
/**
* Fired exactly once, when the VM first starts running.
* <p>
* Fired after all devices reported success from {@link VMDevice#load(VMContext)}.
* Fired after all devices reported success from {@link VMDevice#mount(VMContext)}.
* <p>
* If a running VM is restored from a saved state, this event will not be fired. It is
* intended for initializing the VM state on boot, e.g. by loading initial executable
@@ -14,7 +14,7 @@ import li.cil.oc2.api.bus.device.vm.context.VMContext;
* <p>
* Listeners of this event may throw a {@link VMInitializationException} in case
* initialization fails. For some devices it may be too costly to perform a full
* validity check in {@link VMDevice#load(VMContext)}. These devices may still cause
* validity check in {@link VMDevice#mount(VMContext)}. These devices may still cause
* a startup to fail this way.
* <p>
* <em>This is invoked from the worker thread running the VM.</em>

View File

@@ -6,7 +6,7 @@ import li.cil.oc2.api.bus.device.vm.context.VMContext;
/**
* Fired when the VM resumes running.
* <p>
* Fired after all devices reported success from {@link VMDevice#load(VMContext)}.
* Fired after all devices reported success from {@link VMDevice#mount(VMContext)}.
* <p>
* Fired on initial boot-up as well as when the VM resumes after being restored
* from a saved state as well as when continuing to run after being paused for

View File

@@ -55,7 +55,7 @@ public abstract class AbstractBlockDeviceVMDevice<TBlock extends BlockDevice, TI
///////////////////////////////////////////////////////////////////
@Override
public VMDeviceLoadResult load(final VMContext context) {
public VMDeviceLoadResult mount(final VMContext context) {
data = createBlockDevice();
if (!allocateDevice(context)) {
@@ -84,20 +84,25 @@ public abstract class AbstractBlockDeviceVMDevice<TBlock extends BlockDevice, TI
}
@Override
public void unload() {
public void unmount() {
// Since we cannot serialize the data in a regular serialize call due to the
// actual data being unloaded at that point, but want to permanently persist
// it (it's the contents of the block device) we need to serialize it in the
// unload, too. Don't need to wait for the job, though.
serializeData();
suspend();
deviceTag = null;
address.clear();
interrupt.clear();
}
@Override
public void suspend() {
data = null;
jobHandle = null;
device = null;
deviceTag = null;
address.clear();
interrupt.clear();
}
@Subscribe

View File

@@ -48,7 +48,7 @@ public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack
///////////////////////////////////////////////////////////////
@Override
public VMDeviceLoadResult load(final VMContext context) {
public VMDeviceLoadResult mount(final VMContext context) {
if (!allocateDevice(context)) {
return VMDeviceLoadResult.fail();
}
@@ -67,12 +67,17 @@ public final class ByteBufferFlashMemoryVMDevice extends IdentityProxy<ItemStack
}
@Override
public void unload() {
public void unmount() {
suspend();
deviceTag = null;
address.clear();
}
@Override
public void suspend() {
memoryMap = null;
data = null;
device = null;
deviceTag = null;
address.clear();
}
@Subscribe

View File

@@ -30,7 +30,7 @@ public final class FirmwareFlashMemoryVMDevice extends IdentityProxy<ItemStack>
///////////////////////////////////////////////////////////////
@Override
public VMDeviceLoadResult load(final VMContext context) {
public VMDeviceLoadResult mount(final VMContext context) {
memoryMap = context.getMemoryMap();
context.getEventBus().register(this);
@@ -39,7 +39,12 @@ public final class FirmwareFlashMemoryVMDevice extends IdentityProxy<ItemStack>
}
@Override
public void unload() {
public void unmount() {
suspend();
}
@Override
public void suspend() {
memoryMap = null;
}

View File

@@ -45,7 +45,7 @@ public final class MemoryDevice extends IdentityProxy<ItemStack> implements VMDe
///////////////////////////////////////////////////////////////////
@Override
public VMDeviceLoadResult load(final VMContext context) {
public VMDeviceLoadResult mount(final VMContext context) {
if (!allocateDevice(context)) {
return VMDeviceLoadResult.fail();
}
@@ -62,16 +62,22 @@ public final class MemoryDevice extends IdentityProxy<ItemStack> implements VMDe
}
@Override
public void unload() {
public void unmount() {
suspend();
// Memory is volatile, so free up our persisted blob when device is unloaded.
BlobStorage.freeHandle(blobHandle);
blobHandle = null;
jobHandle = null;
device = null;
address.clear();
}
@Override
public void suspend() {
device = null;
}
@Subscribe
public void handleResumingRunningEvent(final VMResumingRunningEvent event) {
awaitStorageOperation();

View File

@@ -60,7 +60,7 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStac
}
@Override
public VMDeviceLoadResult load(final VMContext context) {
public VMDeviceLoadResult mount(final VMContext context) {
device = new VirtIONetworkDevice(context.getMemoryMap());
if (!address.claim(context, device)) {
@@ -83,13 +83,18 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy<ItemStac
}
@Override
public void unload() {
device = null;
public void unmount() {
suspend();
isRunning = false;
address.clear();
interrupt.clear();
}
@Override
public void suspend() {
device = null;
}
@Subscribe
public void handlePausingEvent(final VMPausingEvent event) {
isRunning = false;

View File

@@ -322,10 +322,10 @@ public final class RobotEntity extends Entity implements Robot {
public void remove(final boolean keepData) {
super.remove(keepData);
handleUnload();
virtualMachine.suspend();
// Full unload to release out-of-nbt persisted runtime-only data such as ram.
virtualMachine.state.vmAdapter.unload();
virtualMachine.state.vmAdapter.unmount();
}
@Override
@@ -449,7 +449,7 @@ public final class RobotEntity extends Entity implements Robot {
}
unregisterListeners();
handleUnload();
virtualMachine.suspend();
}
private void handleWorldUnload(final WorldEvent.Unload event) {
@@ -458,11 +458,7 @@ public final class RobotEntity extends Entity implements Robot {
}
unregisterListeners();
handleUnload();
}
private void handleUnload() {
virtualMachine.unload();
virtualMachine.suspend();
}
private void openTerminalScreen(final ServerPlayerEntity player) {

View File

@@ -242,7 +242,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
// super.remove() calls onUnload. This in turn only suspends, but we want to do
// a full clean-up when we get destroyed, so stuff inside us can delete out-of-nbt
// persisted runtime-only data such as ram.
virtualMachine.state.vmAdapter.unload();
virtualMachine.state.vmAdapter.unmount();
}
@Override
@@ -333,7 +333,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
protected void unloadServer() {
super.unloadServer();
virtualMachine.unload();
virtualMachine.suspend();
// This is necessary in case some other controller found us before our controller
// did its scan, which can happen because the scan can happen with a delay. In

View File

@@ -81,7 +81,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
///////////////////////////////////////////////////////////////////
public void unload() {
public void suspend() {
joinWorkerThread();
state.vmAdapter.suspend();
state.context.invalidate();
@@ -202,7 +202,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
state.board.reset();
state.rpcAdapter.reset();
state.vmAdapter.unload();
state.vmAdapter.unmount();
runner = null;
}
@@ -306,7 +306,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine {
return;
}
final VMDeviceLoadResult loadResult = state.vmAdapter.load();
final VMDeviceLoadResult loadResult = state.vmAdapter.mount();
if (!loadResult.wasSuccessful()) {
if (loadResult.getErrorMessage() != null) {
error(loadResult.getErrorMessage(), false);

View File

@@ -34,7 +34,7 @@ public final class VMDeviceBusAdapter {
baseAddressProvider = provider;
}
public VMDeviceLoadResult load() {
public VMDeviceLoadResult mount() {
for (int i = 0; i < incompleteLoads.size(); i++) {
final VMDevice device = incompleteLoads.get(i);
@@ -43,7 +43,7 @@ public final class VMDeviceBusAdapter {
deviceContexts.put(device, context);
final VMDeviceLoadResult result = device.load(context);
final VMDeviceLoadResult result = device.mount(context);
context.freeze();
if (!result.wasSuccessful()) {
@@ -61,23 +61,20 @@ public final class VMDeviceBusAdapter {
return VMDeviceLoadResult.success();
}
public void unload() {
public void unmount() {
for (final VMDevice device : deviceContexts.keySet()) {
device.unload();
device.unmount();
}
suspend();
unload();
}
public void suspend() {
deviceContexts.forEach((device, context) -> {
if (context != null) {
context.invalidate();
}
});
for (final VMDevice device : deviceContexts.keySet()) {
device.suspend();
}
incompleteLoads.clear();
incompleteLoads.addAll(deviceContexts.keySet());
unload();
}
public void addDevices(final Collection<Device> devices) {
@@ -100,7 +97,7 @@ public final class VMDeviceBusAdapter {
if (device instanceof VMDevice) {
final VMDevice vmDevice = (VMDevice) device;
vmDevice.unload();
vmDevice.unmount();
final ManagedVMContext context = deviceContexts.remove(vmDevice);
if (context != null) {
@@ -111,4 +108,17 @@ public final class VMDeviceBusAdapter {
}
}
}
///////////////////////////////////////////////////////////////////
private void unload() {
deviceContexts.forEach((device, context) -> {
if (context != null) {
context.invalidate();
}
});
incompleteLoads.clear();
incompleteLoads.addAll(deviceContexts.keySet());
}
}

View File

@@ -63,64 +63,64 @@ public final class VMDeviceTests {
public void addedDevicesHaveLoadCalled() {
final VMDevice device1 = mock(VMDevice.class);
final VMDevice device2 = mock(VMDevice.class);
when(device1.load(any())).thenReturn(VMDeviceLoadResult.success());
when(device2.load(any())).thenReturn(VMDeviceLoadResult.success());
when(device1.mount(any())).thenReturn(VMDeviceLoadResult.success());
when(device2.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device1));
assertTrue(adapter.load().wasSuccessful());
verify(device1).load(any());
assertTrue(adapter.mount().wasSuccessful());
verify(device1).mount(any());
adapter.addDevices(Collections.singleton(device2));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
verifyNoMoreInteractions(device1);
verify(device2).load(any());
verify(device2).mount(any());
}
@Test
public void removedDevicesHaveUnloadCalled() {
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
when(device.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
adapter.removeDevices(Collections.singleton(device));
verify(device).unload();
verify(device).unmount();
}
@Test
public void devicesHaveUnloadCalledOnGlobalUnload() {
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
when(device.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
adapter.unload();
verify(device).unload();
adapter.unmount();
verify(device).unmount();
}
@Test
public void devicesHaveLoadCalledAfterGlobalUnload() {
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenReturn(VMDeviceLoadResult.success());
when(device.mount(any())).thenReturn(VMDeviceLoadResult.success());
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
verify(device).load(any());
assertTrue(adapter.mount().wasSuccessful());
verify(device).mount(any());
adapter.unload();
verify(device).unload();
adapter.unmount();
verify(device).unmount();
assertTrue(adapter.load().wasSuccessful());
verify(device, times(2)).load(any());
assertTrue(adapter.mount().wasSuccessful());
verify(device, times(2)).mount(any());
}
@Test
public void deviceCanClaimInterrupts() {
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt();
assertTrue(interrupt.isPresent());
@@ -128,9 +128,9 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
verify(device).load(any());
verify(device).mount(any());
}
@Test
@@ -138,7 +138,7 @@ public final class VMDeviceTests {
final int claimedInterrupt = 1;
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final boolean result = context.getInterruptAllocator().claimInterrupt(claimedInterrupt);
assertFalse(result);
@@ -148,14 +148,14 @@ public final class VMDeviceTests {
context.getInterruptAllocator().claimInterrupt(claimedInterrupt);
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
}
@Test
public void deviceCanRaiseClaimedInterrupts() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt();
assertTrue(interrupt.isPresent());
@@ -167,7 +167,7 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
final int claimedInterruptMask = 1 << deviceData.interrupt;
deviceData.context.getInterruptController().raiseInterrupts(claimedInterruptMask);
@@ -179,13 +179,13 @@ public final class VMDeviceTests {
public void devicesCannotRaiseUnclaimedInterrupts() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
deviceData.context = invocation.getArgument(0);
return VMDeviceLoadResult.success();
});
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
final int someInterruptMask = 0x1;
assertThrows(IllegalArgumentException.class, () ->
@@ -196,7 +196,7 @@ public final class VMDeviceTests {
public void unloadLowersClaimedInterrupts() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
final OptionalInt interrupt = context.getInterruptAllocator().claimInterrupt();
assertTrue(interrupt.isPresent());
@@ -208,14 +208,14 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
final int claimedInterruptMask = 1 << deviceData.interrupt;
deviceData.context.getInterruptController().raiseInterrupts(claimedInterruptMask);
assertTrue((interruptController.getRaisedInterrupts() & claimedInterruptMask) != 0);
adapter.unload();
adapter.unmount();
assertFalse((interruptController.getRaisedInterrupts() & claimedInterruptMask) != 0);
}
@@ -223,7 +223,7 @@ public final class VMDeviceTests {
@Test
public void devicesCannotAddToMemoryMapDirectly() {
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
assertThrows(UnsupportedOperationException.class, () ->
@@ -233,14 +233,14 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
adapter.load();
adapter.mount();
}
@Test
public void devicesCanAddMemoryMappedDevices() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
deviceData.context = context;
@@ -253,7 +253,7 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
assertTrue(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent());
}
@@ -262,7 +262,7 @@ public final class VMDeviceTests {
public void addedDevicesGetRemovedOnUnload() {
final DeviceData deviceData = new DeviceData();
final VMDevice device = mock(VMDevice.class);
when(device.load(any())).thenAnswer(invocation -> {
when(device.mount(any())).thenAnswer(invocation -> {
final VMContext context = invocation.getArgument(0);
deviceData.context = context;
@@ -275,11 +275,11 @@ public final class VMDeviceTests {
});
adapter.addDevices(Collections.singleton(device));
assertTrue(adapter.load().wasSuccessful());
assertTrue(adapter.mount().wasSuccessful());
assertTrue(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent());
adapter.unload();
adapter.unmount();
assertFalse(deviceData.context.getMemoryMap().getMemoryRange(deviceData.device).isPresent());
}