From d13cdc0cd87e3d6dbaa8655972c283ec484455ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 29 Mar 2015 22:45:43 +0200 Subject: [PATCH] Somewhat experimental fix for memory leaks seen in Microcontrollers. If you notice any new issues after this, please do let me know. This essentially schedules machines for full cleanup at a later point, to allow executor threads to finish (can't close states while they're running, don't want to wait for them to finish to avoid lagging servers). --- .../scala/li/cil/oc/common/EventHandler.scala | 11 +++++- .../common/tileentity/traits/Computer.scala | 5 +++ .../li/cil/oc/server/machine/Machine.scala | 37 +++++++++++++------ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/scala/li/cil/oc/common/EventHandler.scala b/src/main/scala/li/cil/oc/common/EventHandler.scala index 1411e5046..06287183f 100644 --- a/src/main/scala/li/cil/oc/common/EventHandler.scala +++ b/src/main/scala/li/cil/oc/common/EventHandler.scala @@ -22,6 +22,7 @@ import li.cil.oc.common.tileentity.traits.power import li.cil.oc.integration.Mods import li.cil.oc.integration.util import li.cil.oc.server.component.Keyboard +import li.cil.oc.server.machine.Machine import li.cil.oc.server.{PacketSender => ServerPacketSender} import li.cil.oc.util._ import net.minecraft.client.Minecraft @@ -49,12 +50,16 @@ object EventHandler { private val keyboards = java.util.Collections.newSetFromMap[Keyboard](new java.util.WeakHashMap[Keyboard, java.lang.Boolean]) + private val machines = mutable.Set.empty[Machine] + def onRobotStart(robot: Robot): Unit = runningRobots += robot def onRobotStopped(robot: Robot): Unit = runningRobots -= robot def addKeyboard(keyboard: Keyboard): Unit = keyboards += keyboard + def scheduleClose(machine: Machine) = machines += machine + def schedule(tileEntity: TileEntity) { if (SideTracker.isServer) pending.synchronized { pending += (() => Network.joinOrCreateNetwork(tileEntity)) @@ -130,6 +135,10 @@ object EventHandler { else if (robot.world != null) robot.machine.update() }) runningRobots --= invalid + + val closed = mutable.ArrayBuffer.empty[Machine] + machines.foreach(machine => if (machine.tryClose()) closed += machine) + machines --= closed } @SubscribeEvent @@ -243,7 +252,7 @@ object EventHandler { didRecraft = recraft(e, tablet, stack => { // Restore EEPROM currently used in tablet. - new TabletData(stack).items.collect { case Some(item) => item}.find(api.Items.get(_) == eeprom) + new TabletData(stack).items.collect { case Some(item) => item }.find(api.Items.get(_) == eeprom) }) || didRecraft // Presents? diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala index 954634278..1a9eaf34e 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/Computer.scala @@ -127,6 +127,11 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B ServerPacketSender.sendComputerState(this) } + override def dispose(): Unit = { + super.dispose() + if (machine != null) machine.stop() + } + // ----------------------------------------------------------------------- // override def readFromNBTForServer(nbt: NBTTagCompound) { diff --git a/src/main/scala/li/cil/oc/server/machine/Machine.scala b/src/main/scala/li/cil/oc/server/machine/Machine.scala index 3c40bf31c..7f9de376f 100644 --- a/src/main/scala/li/cil/oc/server/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/machine/Machine.scala @@ -23,6 +23,7 @@ import li.cil.oc.api.network.Message import li.cil.oc.api.network.Node import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab +import li.cil.oc.common.EventHandler import li.cil.oc.common.SaveHandler import li.cil.oc.common.Slot import li.cil.oc.common.tileentity @@ -257,6 +258,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach false case _ => state.push(Machine.State.Stopping) + EventHandler.scheduleClose(this) true }) @@ -392,6 +394,8 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach // ----------------------------------------------------------------------- // + def isExecuting = state.synchronized(state.contains(Machine.State.Running)) + override val canUpdate = true override def update() = if (state.synchronized(state.top != Machine.State.Stopped)) { @@ -514,7 +518,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach inSynchronizedCall = false } - assert(state.top != Machine.State.Running) + assert(!isExecuting) case _ => // Nothing special to do, just avoid match errors. }) @@ -643,7 +647,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach super.load(nbt) - state.pushAll(nbt.getIntArray("state").reverse.map(Machine.State(_))) + state.pushAll(nbt.getIntArray("state").reverseMap(Machine.State(_))) nbt.getTagList("users", NBT.TAG_STRING).foreach((tag: NBTTagString) => _users += tag.func_150285_a_()) if (nbt.hasKey("message")) { message = Some(nbt.getString("message")) @@ -703,7 +707,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach } override def save(nbt: NBTTagCompound): Unit = Machine.this.synchronized { - assert(state.top != Machine.State.Running) // Lock on 'this' should guarantee this. + assert(!isExecuting) // Lock on 'this' should guarantee this. // Make sure we don't continue running until everything has saved. pause(0.05) @@ -798,16 +802,25 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach false } + def tryClose(): Boolean = + if (isExecuting) false + else { + close() + true + } + private def close() = state.synchronized( if (state.size == 0 || state.top != Machine.State.Stopped) { - state.clear() - state.push(Machine.State.Stopped) - Option(architecture).foreach(_.close()) - signals.clear() - uptime = 0 - cpuTotal = 0 - cpuStart = 0 - remainIdle = 0 + this.synchronized { + state.clear() + state.push(Machine.State.Stopped) + Option(architecture).foreach(_.close()) + signals.clear() + uptime = 0 + cpuTotal = 0 + cpuStart = 0 + remainIdle = 0 + } // Mark state change in owner, to send it to clients. host.markChanged() @@ -909,7 +922,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach case Machine.State.Stopping => // Nothing to do, we'll die anyway. case _ => throw new AssertionError("Invalid state in executor post-processing.") } - assert(state.top != Machine.State.Running) + assert(!isExecuting) } } catch {