Skip to content

Commit bd2c228

Browse files
authored
DefaultCommand signal behavior improvements for plugins (apple#570)
## Type of Change - [x] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Description Correct signal semantics for plugins: Container binary currently execs into plugin binaries. If the parent CLI keeps SIGINT/SIGTERM handlers installed, it can intercept/alter signal behavior intended for the plugin (e.g., preventing graceful shutdown in foreground workflows). ## Motivation and Context During the development of a plugin (docker compose compatibility plugin), I encountered a major issues where CTRL-C (SIGTERM) was not being sent to my plugin. CLI plugins, especially those that have long running tasks need a way to handle signals from the OS. Current, we exec into plugin binaries. If the parent CLI keeps SIGINT/SIGTERM handlers installed, it can intercept/alter signal behavior intended for the plugin (e.g., preventing graceful shutdown in foreground workflows). ### What we changed: - Signals handed back to plugins: - DefaultCommand resets SIGINT/SIGTERM to defaults immediately before exec’ing the plugin. - Rationale: since exec replaces the process image, signals should be delivered to (and handled by) the plugin without parent interference. - Non‑plugin commands remain unaffected by this change. - Compatibility: No change to plugin ABI or exec flow. ### Alternatives considered: - Supervising child instead of exec: central forwarding of signals from parent to plugin. Rejected for now to avoid changing process tree/stdio semantics; resetting to defaults before exec preserves current model while fixing signal interference. ## Testing - [X] Tested locally - [ ] Added/updated tests - [ ] Added/updated docs
1 parent 7bd9e5a commit bd2c228

File tree

1 file changed

+11
-0
lines changed

1 file changed

+11
-0
lines changed

Sources/CLI/DefaultCommand.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import ArgumentParser
1818
import ContainerClient
1919
import ContainerPlugin
20+
import Darwin
2021

2122
struct DefaultCommand: AsyncParsableCommand {
2223
static let configuration = CommandConfiguration(
@@ -48,7 +49,17 @@ struct DefaultCommand: AsyncParsableCommand {
4849
guard let plugin = pluginLoader?.findPlugin(name: command), plugin.config.isCLI else {
4950
throw ValidationError("failed to find plugin named container-\(command)")
5051
}
52+
// Before execing into the plugin, restore default SIGINT/SIGTERM so the plugin can manage signals.
53+
Self.resetSignalsForPluginExec()
5154
// Exec performs execvp (with no fork).
5255
try plugin.exec(args: remaining)
5356
}
5457
}
58+
59+
extension DefaultCommand {
60+
// Exposed for tests to verify signal reset semantics.
61+
static func resetSignalsForPluginExec() {
62+
signal(SIGINT, SIG_DFL)
63+
signal(SIGTERM, SIG_DFL)
64+
}
65+
}

0 commit comments

Comments
 (0)