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).
This commit is contained in:
Florian Nücke 2015-03-29 22:45:43 +02:00
parent 5e9861d04a
commit d13cdc0cd8
3 changed files with 40 additions and 13 deletions

View File

@ -22,6 +22,7 @@ import li.cil.oc.common.tileentity.traits.power
import li.cil.oc.integration.Mods import li.cil.oc.integration.Mods
import li.cil.oc.integration.util import li.cil.oc.integration.util
import li.cil.oc.server.component.Keyboard 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.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util._ import li.cil.oc.util._
import net.minecraft.client.Minecraft 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 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 onRobotStart(robot: Robot): Unit = runningRobots += robot
def onRobotStopped(robot: Robot): Unit = runningRobots -= robot def onRobotStopped(robot: Robot): Unit = runningRobots -= robot
def addKeyboard(keyboard: Keyboard): Unit = keyboards += keyboard def addKeyboard(keyboard: Keyboard): Unit = keyboards += keyboard
def scheduleClose(machine: Machine) = machines += machine
def schedule(tileEntity: TileEntity) { def schedule(tileEntity: TileEntity) {
if (SideTracker.isServer) pending.synchronized { if (SideTracker.isServer) pending.synchronized {
pending += (() => Network.joinOrCreateNetwork(tileEntity)) pending += (() => Network.joinOrCreateNetwork(tileEntity))
@ -130,6 +135,10 @@ object EventHandler {
else if (robot.world != null) robot.machine.update() else if (robot.world != null) robot.machine.update()
}) })
runningRobots --= invalid runningRobots --= invalid
val closed = mutable.ArrayBuffer.empty[Machine]
machines.foreach(machine => if (machine.tryClose()) closed += machine)
machines --= closed
} }
@SubscribeEvent @SubscribeEvent
@ -243,7 +252,7 @@ object EventHandler {
didRecraft = recraft(e, tablet, stack => { didRecraft = recraft(e, tablet, stack => {
// Restore EEPROM currently used in tablet. // 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 }) || didRecraft
// Presents? // Presents?

View File

@ -127,6 +127,11 @@ trait Computer extends Environment with ComponentInventory with Rotatable with B
ServerPacketSender.sendComputerState(this) ServerPacketSender.sendComputerState(this)
} }
override def dispose(): Unit = {
super.dispose()
if (machine != null) machine.stop()
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def readFromNBTForServer(nbt: NBTTagCompound) { override def readFromNBTForServer(nbt: NBTTagCompound) {

View File

@ -23,6 +23,7 @@ import li.cil.oc.api.network.Message
import li.cil.oc.api.network.Node import li.cil.oc.api.network.Node
import li.cil.oc.api.network.Visibility import li.cil.oc.api.network.Visibility
import li.cil.oc.api.prefab import li.cil.oc.api.prefab
import li.cil.oc.common.EventHandler
import li.cil.oc.common.SaveHandler import li.cil.oc.common.SaveHandler
import li.cil.oc.common.Slot import li.cil.oc.common.Slot
import li.cil.oc.common.tileentity import li.cil.oc.common.tileentity
@ -257,6 +258,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach
false false
case _ => case _ =>
state.push(Machine.State.Stopping) state.push(Machine.State.Stopping)
EventHandler.scheduleClose(this)
true 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 val canUpdate = true
override def update() = if (state.synchronized(state.top != Machine.State.Stopped)) { 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 inSynchronizedCall = false
} }
assert(state.top != Machine.State.Running) assert(!isExecuting)
case _ => // Nothing special to do, just avoid match errors. 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) 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_()) nbt.getTagList("users", NBT.TAG_STRING).foreach((tag: NBTTagString) => _users += tag.func_150285_a_())
if (nbt.hasKey("message")) { if (nbt.hasKey("message")) {
message = Some(nbt.getString("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 { 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. // Make sure we don't continue running until everything has saved.
pause(0.05) pause(0.05)
@ -798,16 +802,25 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach
false false
} }
def tryClose(): Boolean =
if (isExecuting) false
else {
close()
true
}
private def close() = state.synchronized( private def close() = state.synchronized(
if (state.size == 0 || state.top != Machine.State.Stopped) { if (state.size == 0 || state.top != Machine.State.Stopped) {
state.clear() this.synchronized {
state.push(Machine.State.Stopped) state.clear()
Option(architecture).foreach(_.close()) state.push(Machine.State.Stopped)
signals.clear() Option(architecture).foreach(_.close())
uptime = 0 signals.clear()
cpuTotal = 0 uptime = 0
cpuStart = 0 cpuTotal = 0
remainIdle = 0 cpuStart = 0
remainIdle = 0
}
// Mark state change in owner, to send it to clients. // Mark state change in owner, to send it to clients.
host.markChanged() 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 Machine.State.Stopping => // Nothing to do, we'll die anyway.
case _ => throw new AssertionError("Invalid state in executor post-processing.") case _ => throw new AssertionError("Invalid state in executor post-processing.")
} }
assert(state.top != Machine.State.Running) assert(!isExecuting)
} }
} }
catch { catch {