diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java
new file mode 100644
index 00000000..c09543e3
--- /dev/null
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java
@@ -0,0 +1,14 @@
+package li.cil.oc2.api.bus.device.vm;
+
+/**
+ * A memory allocator used to ensure sandbox limits when loading devices.
+ */
+public interface MemoryAllocator {
+ /**
+ * Tries to reserve the specified amount of memory, in bytes.
+ *
+ * @param size the amount of memory to reserve, in bytes.
+ * @return {@code true} when the memory was claimed successfully; {@code false} otherwise.
+ */
+ boolean claimMemory(int size);
+}
diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java
index d990041a..35545825 100644
--- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java
+++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java
@@ -29,7 +29,8 @@ public interface VMContext {
* made available through this instance will result in an exception.
*
* Interrupts raised will automatically be lowered when the {@link VMDevice} that
- * raised them is unloaded, e.g. because it is removed from the {@link DeviceBus}.
+ * raised them is unloaded, e.g. because it is removed from the {@link DeviceBus}
+ * or the VM stopped.
*
* @return the interrupt controller of the virtual machine.
*/
@@ -42,7 +43,8 @@ public interface VMContext {
* Trying to add devices after that method has returned will result in an exception.
*
* Added devices will be automatically removed when the {@link VMDevice} that added it
- * is unloaded, e.g. because it has been removed from the {@link DeviceBus}.
+ * is unloaded, e.g. because it has been removed from the {@link DeviceBus} or the VM
+ * stopped.
*
* @return the memory range allocator.
*/
@@ -60,4 +62,23 @@ public interface VMContext {
* @return the interrupt allocator.
*/
InterruptAllocator getInterruptAllocator();
+
+ /**
+ * Allows reserving fixed amounts of memory respecting sandbox constraints.
+ *
+ * It is strongly advised to use this allocator to make known large memory
+ * uses, e.g. when allocating large blobs for RAM or block devices. This
+ * allows respecting the built-in limits for overall memory usage of
+ * running VMs.
+ *
+ * Devices failing to reserve the memory they would use should fail their
+ * {@link VMDevice#load(VMContext)}.
+ *
+ * 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
+ * VM stopped.
+ *
+ * @return the memory allocator.
+ */
+ MemoryAllocator getMemoryAllocator();
}
diff --git a/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java b/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java
index 6befa750..53f69961 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java
@@ -5,7 +5,6 @@ import li.cil.oc2.common.bus.device.provider.util.AbstractObjectProxy;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.serialization.NBTSerialization;
import li.cil.oc2.common.util.NBTTagIds;
-import li.cil.oc2.common.vm.Allocator;
import li.cil.sedna.api.device.BlockDevice;
import li.cil.sedna.device.virtio.VirtIOBlockDevice;
import net.minecraft.item.ItemStack;
@@ -28,7 +27,6 @@ public abstract class AbstractHardDiskDriveDevice extends
///////////////////////////////////////////////////////////////
- private final UUID allocHandle = Allocator.createHandle();
private BlobStorage.JobHandle jobHandle;
private T data;
private VirtIOBlockDevice device;
@@ -56,12 +54,10 @@ public abstract class AbstractHardDiskDriveDevice extends
}
if (!claimAddress(context)) {
- Allocator.freeMemory(allocHandle);
return VMDeviceLoadResult.fail();
}
if (!claimInterrupt(context)) {
- Allocator.freeMemory(allocHandle);
return VMDeviceLoadResult.fail();
}
@@ -152,7 +148,7 @@ public abstract class AbstractHardDiskDriveDevice extends
///////////////////////////////////////////////////////////////
private boolean allocateDevice(final VMContext context) {
- if (!Allocator.claimMemory(allocHandle, getSize())) {
+ if (!context.getMemoryAllocator().claimMemory(getSize())) {
return false;
}
@@ -231,12 +227,12 @@ public abstract class AbstractHardDiskDriveDevice extends
}
}
- Allocator.freeMemory(allocHandle);
data = null;
device = null;
deviceNbt = null;
address = null;
interrupt = null;
+ jobHandle = null;
}
}
diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java b/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java
index 06637e7c..a235cdf8 100644
--- a/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java
+++ b/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java
@@ -9,7 +9,6 @@ import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.util.AbstractObjectProxy;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.util.NBTTagIds;
-import li.cil.oc2.common.vm.Allocator;
import li.cil.sedna.api.device.PhysicalMemory;
import li.cil.sedna.device.memory.Memory;
import li.cil.sedna.memory.PhysicalMemoryInputStream;
@@ -46,7 +45,6 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
///////////////////////////////////////////////////////////////
- private final UUID allocHandle = Allocator.createHandle();
private BlobStorage.JobHandle jobHandle;
private PhysicalMemory device;
@@ -63,7 +61,7 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
@Override
public VMDeviceLoadResult load(final VMContext context) {
- if (!allocateDevice()) {
+ if (!allocateDevice(context)) {
return VMDeviceLoadResult.fail();
}
@@ -122,8 +120,8 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
///////////////////////////////////////////////////////////////
- private boolean allocateDevice() {
- if (!Allocator.claimMemory(allocHandle, RAM_SIZE)) {
+ private boolean allocateDevice(final VMContext context) {
+ if (!context.getMemoryAllocator().claimMemory(RAM_SIZE)) {
return false;
}
@@ -141,7 +139,6 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
}
if (!claimedAddress.isPresent()) {
- Allocator.freeMemory(allocHandle);
return false;
}
@@ -164,17 +161,13 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
}
private void unload() {
- // Finish saves on unload to ensure future loads will read correct data.
- awaitStorageOperation();
-
- Allocator.freeMemory(allocHandle);
- device = null;
-
// RAM is volatile, so free up our persisted blob when device is unloaded.
BlobStorage.freeHandle(blobHandle);
blobHandle = null;
+ device = null;
address = null;
+ jobHandle = null;
}
}
}
diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java
new file mode 100644
index 00000000..d7c7ae06
--- /dev/null
+++ b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java
@@ -0,0 +1,38 @@
+package li.cil.oc2.common.vm;
+
+import li.cil.oc2.api.bus.device.vm.MemoryAllocator;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+public final class ManagedMemoryAllocator implements MemoryAllocator {
+ private final ArrayList claimedMemory = new ArrayList<>();
+ private boolean isFrozen;
+
+ public void freeze() {
+ isFrozen = true;
+ }
+
+ public void invalidate() {
+ for (final UUID handle : claimedMemory) {
+ Allocator.freeMemory(handle);
+ }
+
+ claimedMemory.clear();
+ }
+
+ @Override
+ public boolean claimMemory(final int size) {
+ if (isFrozen) {
+ throw new IllegalStateException();
+ }
+
+ final UUID handle = Allocator.createHandle();
+ if (!Allocator.claimMemory(handle, size)) {
+ return false;
+ }
+
+ claimedMemory.add(handle);
+ return true;
+ }
+}
diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java
index 07cbb145..a23dc072 100644
--- a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java
+++ b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java
@@ -1,6 +1,7 @@
package li.cil.oc2.common.vm;
import li.cil.oc2.api.bus.device.vm.InterruptAllocator;
+import li.cil.oc2.api.bus.device.vm.MemoryAllocator;
import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.sedna.api.Board;
@@ -14,6 +15,7 @@ public final class ManagedVMContext implements VMContext {
private final ManagedInterruptController interruptController;
private final ManagedMemoryRangeAllocator memoryRangeAllocator;
private final ManagedInterruptAllocator interruptAllocator;
+ private final ManagedMemoryAllocator memoryAllocator;
///////////////////////////////////////////////////////////////////
@@ -22,17 +24,20 @@ public final class ManagedVMContext implements VMContext {
this.interruptAllocator = new ManagedInterruptAllocator(claimedInterrupts, reservedInterrupts, board.getInterruptCount());
this.memoryMap = new ManagedMemoryMap(board.getMemoryMap());
this.interruptController = new ManagedInterruptController(board.getInterruptController(), interruptAllocator);
+ this.memoryAllocator = new ManagedMemoryAllocator();
}
public void freeze() {
memoryRangeAllocator.freeze();
interruptAllocator.freeze();
+ memoryAllocator.freeze();
}
public void invalidate() {
memoryRangeAllocator.invalidate();
interruptAllocator.invalidate();
interruptController.invalidate();
+ memoryAllocator.invalidate();
}
@Override
@@ -54,4 +59,9 @@ public final class ManagedVMContext implements VMContext {
public InterruptAllocator getInterruptAllocator() {
return interruptAllocator;
}
+
+ @Override
+ public MemoryAllocator getMemoryAllocator() {
+ return memoryAllocator;
+ }
}