mirror of
https://github.com/apple/container.git
synced 2026-07-13 13:07:06 +00:00
Fix container auto-delete on rapid stop/start (#841)
Fixes #833. Currently, when stopping and immediately restarting a container, it would fail with the error: `“container expected to be in created state, got: shuttingDown”` and then be automatically deleted. The `SandboxService` process waits five seconds before exiting after shutdown. During this interval, a rapid restart could reconnect to the still-terminating process in the `shuttingDown` state, triggering a state validation error. This fix forcefully terminates the `SandboxService` process with `SIGKILL` upon container exit, instead of waiting five seconds. The bootstrap now defensively checks for and cleans up any stale services before registering new ones, preventing reconnections to processes in the `shuttingDown` state.
This commit is contained in:
@@ -90,19 +90,12 @@ public actor ContainersService {
|
||||
)
|
||||
)
|
||||
results[config.id] = state
|
||||
let plugin = runtimePlugins.first { $0.name == config.runtimeHandler }
|
||||
guard let plugin else {
|
||||
guard runtimePlugins.first(where: { $0.name == config.runtimeHandler }) != nil else {
|
||||
throw ContainerizationError(
|
||||
.internalError,
|
||||
message: "failed to find runtime plugin \(config.runtimeHandler)"
|
||||
)
|
||||
}
|
||||
try Self.registerService(
|
||||
plugin: plugin,
|
||||
loader: loader,
|
||||
configuration: config,
|
||||
path: dir
|
||||
)
|
||||
} catch {
|
||||
try? FileManager.default.removeItem(at: dir)
|
||||
log.warning("failed to load container bundle at \(dir.path)")
|
||||
@@ -229,10 +222,7 @@ public actor ContainersService {
|
||||
)
|
||||
}
|
||||
|
||||
let runtimePlugin = self.runtimePlugins.filter {
|
||||
$0.name == configuration.runtimeHandler
|
||||
}.first
|
||||
guard let runtimePlugin else {
|
||||
guard self.runtimePlugins.first(where: { $0.name == configuration.runtimeHandler }) != nil else {
|
||||
throw ContainerizationError(
|
||||
.notFound,
|
||||
message: "unable to locate runtime plugin \(configuration.runtimeHandler)"
|
||||
@@ -255,13 +245,6 @@ public actor ContainersService {
|
||||
try bundle.setContainerRootFs(cloning: imageFs)
|
||||
try bundle.write(filename: "options.json", value: options)
|
||||
|
||||
try Self.registerService(
|
||||
plugin: runtimePlugin,
|
||||
loader: self.pluginLoader,
|
||||
configuration: configuration,
|
||||
path: path
|
||||
)
|
||||
|
||||
let snapshot = ContainerSnapshot(
|
||||
configuration: configuration,
|
||||
status: .stopped,
|
||||
@@ -293,6 +276,16 @@ public actor ContainersService {
|
||||
return
|
||||
}
|
||||
|
||||
let path = self.containerRoot.appendingPathComponent(id)
|
||||
let bundle = ContainerClient.Bundle(path: path)
|
||||
let config = try bundle.configuration
|
||||
try Self.registerService(
|
||||
plugin: self.runtimePlugins.first { $0.name == config.runtimeHandler }!,
|
||||
loader: self.pluginLoader,
|
||||
configuration: config,
|
||||
path: path
|
||||
)
|
||||
|
||||
let runtime = state.snapshot.configuration.runtimeHandler
|
||||
let sandboxClient = try await SandboxClient.create(
|
||||
id: id,
|
||||
@@ -536,15 +529,23 @@ public actor ContainersService {
|
||||
|
||||
await self.exitMonitor.stopTracking(id: id)
|
||||
|
||||
// Try and shutdown the runtime helper.
|
||||
do {
|
||||
self.log.info("Shutting down sandbox service for \(id)")
|
||||
// Shutdown and deregister the sandbox service
|
||||
self.log.info("Shutting down sandbox service for \(id)")
|
||||
|
||||
let client = try state.getClient()
|
||||
try await client.shutdown()
|
||||
} catch {
|
||||
self.log.error("failed to shutdown sandbox service for \(id): \(error)")
|
||||
}
|
||||
let path = self.containerRoot.appendingPathComponent(id)
|
||||
let bundle = ContainerClient.Bundle(path: path)
|
||||
let config = try bundle.configuration
|
||||
let label = Self.fullLaunchdServiceLabel(
|
||||
runtimeName: config.runtimeHandler,
|
||||
instanceId: id
|
||||
)
|
||||
|
||||
let client = try state.getClient()
|
||||
try await client.shutdown()
|
||||
|
||||
// Deregister the service, launchd will terminate the process
|
||||
try ServiceManager.deregister(fullServiceLabel: label)
|
||||
self.log.info("Deregistered sandbox service for \(id)")
|
||||
|
||||
state.snapshot.status = .stopped
|
||||
state.snapshot.networks = []
|
||||
|
||||
@@ -310,15 +310,6 @@ public actor SandboxService {
|
||||
case .created, .stopped(_), .stopping:
|
||||
await self.setState(.shuttingDown)
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await Task.sleep(for: .seconds(5))
|
||||
} catch {
|
||||
self.log.error("failed to sleep before shutting down SandboxService: \(error)")
|
||||
}
|
||||
self.log.info("Shutting down SandboxService")
|
||||
exit(0)
|
||||
}
|
||||
default:
|
||||
throw ContainerizationError(
|
||||
.invalidState,
|
||||
|
||||
Reference in New Issue
Block a user