From 2ea2665e4e8ee53bda51501a151ab76af44c3721 Mon Sep 17 00:00:00 2001 From: Jaewon Hur Date: Mon, 26 Jan 2026 13:41:53 -0800 Subject: [PATCH 1/2] Shutdown VM gracefully from created state --- Sources/Containerization/LinuxContainer.swift | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/Sources/Containerization/LinuxContainer.swift b/Sources/Containerization/LinuxContainer.swift index c0622049..6e1a22ec 100644 --- a/Sources/Containerization/LinuxContainer.swift +++ b/Sources/Containerization/LinuxContainer.swift @@ -606,12 +606,22 @@ extension LinuxContainer { return } - let startedState = try state.startedState("stop") - let vm = startedState.vm + let vm: any VirtualMachineInstance + let relayManager: UnixSocketRelayManager + + let startedState = try? state.startedState("stop") + if let startedState { + vm = startedState.vm + relayManager = startedState.relayManager + } else { + let createdState = try state.createdState("stop") + vm = createdState.vm + relayManager = createdState.relayManager + } var firstError: Error? do { - try await startedState.relayManager.stopAll() + try await relayManager.stopAll() } catch { self.logger?.error("failed to stop relay manager: \(error)") firstError = firstError ?? error @@ -634,15 +644,17 @@ extension LinuxContainer { } } - // Now lets ensure every process is donezo. - try await agent.kill(pid: -1, signal: SIGKILL) + if let _ = startedState { + // Now lets ensure every process is donezo. + try await agent.kill(pid: -1, signal: SIGKILL) - // Wait on init proc exit. Give it 5 seconds of leeway. - _ = try await agent.waitProcess( - id: self.id, - containerID: self.id, - timeoutInSeconds: 5 - ) + // Wait on init proc exit. Give it 5 seconds of leeway. + _ = try await agent.waitProcess( + id: self.id, + containerID: self.id, + timeoutInSeconds: 5 + ) + } // Today, we leave EBUSY looping and other fun logic up to the // guest agent. @@ -658,25 +670,27 @@ extension LinuxContainer { firstError = firstError ?? error } - for process in startedState.vendedProcesses.values { + if let startedState { + for process in startedState.vendedProcesses.values { + do { + try await process._delete() + } catch { + self.logger?.error("failed to delete process \(process.id): \(error)") + firstError = firstError ?? error + } + } + do { - try await process._delete() + try await startedState.process.delete() } catch { - self.logger?.error("failed to delete process \(process.id): \(error)") + self.logger?.error("failed to delete init process: \(error)") firstError = firstError ?? error } - } - do { - try await startedState.process.delete() - } catch { - self.logger?.error("failed to delete init process: \(error)") - firstError = firstError ?? error + // Clean up file mount temporary directories. + startedState.fileMountContext.cleanup() } - // Clean up file mount temporary directories. - startedState.fileMountContext.cleanup() - do { try await vm.stop() state = .stopped From 6cc45df4a03df2e9af2d3f1dbc398bac3b79c99a Mon Sep 17 00:00:00 2001 From: Jaewon Date: Mon, 26 Jan 2026 14:03:45 -0800 Subject: [PATCH 2/2] Cleanup fileMountContext --- Sources/Containerization/LinuxContainer.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/Containerization/LinuxContainer.swift b/Sources/Containerization/LinuxContainer.swift index 6e1a22ec..6d76fd28 100644 --- a/Sources/Containerization/LinuxContainer.swift +++ b/Sources/Containerization/LinuxContainer.swift @@ -608,15 +608,18 @@ extension LinuxContainer { let vm: any VirtualMachineInstance let relayManager: UnixSocketRelayManager + let fileMountContext: FileMountContext let startedState = try? state.startedState("stop") if let startedState { vm = startedState.vm relayManager = startedState.relayManager + fileMountContext = startedState.fileMountContext } else { let createdState = try state.createdState("stop") vm = createdState.vm relayManager = createdState.relayManager + fileMountContext = createdState.fileMountContext } var firstError: Error? @@ -686,11 +689,11 @@ extension LinuxContainer { self.logger?.error("failed to delete init process: \(error)") firstError = firstError ?? error } - - // Clean up file mount temporary directories. - startedState.fileMountContext.cleanup() } + // Clean up file mount temporary directories. + fileMountContext.cleanup() + do { try await vm.stop() state = .stopped