attempting to fix VM reboot bug

This commit is contained in:
minjaesong
2026-05-04 15:44:59 +09:00
parent 2dcdff83c8
commit 4ff48bba1c
8 changed files with 208 additions and 55 deletions

View File

@@ -325,7 +325,13 @@ class VM(
}
fun killAllContexts() {
contexts.forEach { it.interrupt() }
// Snapshot first: interrupt() can race with the worker thread mutating `contexts`
// (see Parallel.kill / attachProgram) and we want to wait on every one of them.
val snapshot = contexts.toList()
snapshot.forEach { it.interrupt() }
snapshot.forEach {
try { it.join(500L) } catch (_: InterruptedException) { Thread.currentThread().interrupt() }
}
contexts.clear()
}

View File

@@ -22,6 +22,16 @@ object VMSetupBroker {
* @param coroutineJobs Hashmap on the host of VMs that holds the coroutine-job object for the currently running VM-instance. Key: Int(VM's identifier), value: [kotlin.coroutines.Job]
*/
fun initVMenv(vm: VM, profileJson: JsonValue, profileName: String, gpu: GraphicsAdapter, vmRunners: HashMap<VmId, VMRunner>, coroutineJobs: HashMap<VmId, Thread>, whatToDoOnVmException: (Throwable) -> Unit) {
// Refuse to start a new runner while the previous one is still alive:
// running both concurrently would race on the VM's memory / IO and lead
// to mixed text input, garbled rendering, and SIGSEGV on disposed peripherals.
coroutineJobs[vm.id]?.let { old ->
if (old.isAlive) {
System.err.println("[VMSetupBroker] previous runner for ${vm.id} is still alive; tearing it down before re-init")
killVMenv(vm, vmRunners, coroutineJobs)
}
}
vm.init()
try {
@@ -61,9 +71,38 @@ object VMSetupBroker {
*/
fun killVMenv(vm: VM, vmRunners: HashMap<VmId, VMRunner>, coroutineJobs: HashMap<VmId, Thread>) {
// Order is critical: stop ALL execution first, then dispose peripherals.
// If we disposed peripherals while the runner thread is still alive, the
// thread would touch destroyed UnsafePtrs and SIGSEGV.
// 1. Stop parallel/child contexts. park() interrupts and joins them.
vm.park()
vm.poke(-90L, -128)
// 2. Interrupt the main runner thread and cancel the GraalVM context.
// context.close(true) cancels in-flight script evaluation.
val runnerThread = coroutineJobs[vm.id]
runnerThread?.interrupt()
try { vmRunners[vm.id]?.close() } catch (_: Throwable) {}
// 3. Wait for the main runner thread to actually finish.
if (runnerThread != null && runnerThread !== Thread.currentThread()) {
try {
runnerThread.join(2000L)
if (runnerThread.isAlive) {
// Last resort: re-interrupt and accept that disposal will
// happen with the thread still alive. This is logged so
// diagnostics surface a stuck VM rather than failing silently.
System.err.println("[VMSetupBroker] runner ${vm.id} did not exit within 2s; proceeding anyway")
runnerThread.interrupt()
}
}
catch (_: InterruptedException) {
Thread.currentThread().interrupt()
}
}
// 4. Now it's safe to release native resources held by peripherals.
for (i in 1 until vm.peripheralTable.size) {
try {
vm.peripheralTable[i].peripheral?.dispose()
@@ -71,8 +110,9 @@ object VMSetupBroker {
catch (_: Throwable) {}
}
coroutineJobs[vm.id]?.interrupt()
vmRunners[vm.id]?.close()
// 5. Drop runner / job handles so a subsequent initVMenv won't see stale entries.
vmRunners.remove(vm.id)
coroutineJobs.remove(vm.id)
vm.getPrintStream = { TODO() }
vm.getErrorStream = { TODO() }

View File

@@ -1362,8 +1362,12 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
textCursorIsOn = !textCursorIsOn
}
// force light cursor up while typing
textCursorIsOn = textCursorIsOn || ((1..254).any { Gdx.input.isKeyPressed(it) })
// force light cursor up while typing -- only honour global key state when
// this VM is the focused viewport; otherwise hidden VMs would react to
// keypresses meant for the focused one.
if (Gdx.input.inputProcessor === vm.getIO()) {
textCursorIsOn = textCursorIsOn || ((1..254).any { Gdx.input.isKeyPressed(it) })
}
}

View File

@@ -285,24 +285,35 @@ class IOSpace(val vm: VM) : PeriBase("io"), InputProcessor {
private var rtc = 0L
fun update(delta: Float) {
// Only the VM whose IOSpace is wired up as the active InputProcessor (i.e. the
// currently focused viewport) may observe global keyboard/mouse state. Otherwise
// hidden VMs would all see the same keypresses as the focused one.
val isFocused = Gdx.input.inputProcessor === this
if (rawInputFunctionLatched) {
rawInputFunctionLatched = false
// store mouse info
mouseX = (Gdx.input.x + guiPosX).toShort()
mouseY = (Gdx.input.y + guiPosY).toShort()
mouseDown = Gdx.input.isTouched
// strobe keys to fill the key read buffer
var keysPushed = 0
keyEventBuffers.fill(0)
for (k in 1..254) {
if (Gdx.input.isKeyPressed(k)) {
keyEventBuffers[keysPushed] = k.toByte()
keysPushed += 1
}
if (keysPushed >= 8) break
if (isFocused) {
// store mouse info
mouseX = (Gdx.input.x + guiPosX).toShort()
mouseY = (Gdx.input.y + guiPosY).toShort()
mouseDown = Gdx.input.isTouched
// strobe keys to fill the key read buffer
var keysPushed = 0
for (k in 1..254) {
if (Gdx.input.isKeyPressed(k)) {
keyEventBuffers[keysPushed] = k.toByte()
keysPushed += 1
}
if (keysPushed >= 8) break
}
}
else {
mouseDown = false
}
}
@@ -316,26 +327,33 @@ class IOSpace(val vm: VM) : PeriBase("io"), InputProcessor {
rtc = vm.worldInterface.currentTimeInMills()
}
// SIGTERM key combination: Ctrl+Shift+T+R
vm.stopDown = (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.T) &&
Gdx.input.isKeyPressed(Input.Keys.R)) || Gdx.input.isKeyPressed(Input.Keys.PAUSE)
if (vm.stopDown) println("[VM-${vm.id}] SIGTERM requested")
if (isFocused) {
// SIGTERM key combination: Ctrl+Shift+T+R
vm.stopDown = (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.T) &&
Gdx.input.isKeyPressed(Input.Keys.R)) || Gdx.input.isKeyPressed(Input.Keys.PAUSE)
if (vm.stopDown) println("[VM-${vm.id}] SIGTERM requested")
// RESET key combination: Ctrl+Shift+R+S
vm.resetDown = Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.R) &&
Gdx.input.isKeyPressed(Input.Keys.S)
if (vm.resetDown) println("[VM-${vm.id}] RESET requested")
// RESET key combination: Ctrl+Shift+R+S
vm.resetDown = Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.R) &&
Gdx.input.isKeyPressed(Input.Keys.S)
if (vm.resetDown) println("[VM-${vm.id}] RESET requested")
// SYSRQ key combination: Ctrl+Shift+S+Q
vm.sysrqDown = (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.Q) &&
Gdx.input.isKeyPressed(Input.Keys.S)) || Gdx.input.isKeyPressed(Input.Keys.PRINT_SCREEN)
if (vm.sysrqDown) println("[VM-${vm.id}] SYSRQ requested")
// SYSRQ key combination: Ctrl+Shift+S+Q
vm.sysrqDown = (Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) &&
Gdx.input.isKeyPressed(Input.Keys.Q) &&
Gdx.input.isKeyPressed(Input.Keys.S)) || Gdx.input.isKeyPressed(Input.Keys.PRINT_SCREEN)
if (vm.sysrqDown) println("[VM-${vm.id}] SYSRQ requested")
}
else {
vm.stopDown = false
vm.resetDown = false
vm.sysrqDown = false
}
}
override fun touchUp(p0: Int, p1: Int, p2: Int, p3: Int): Boolean {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB