-
Notifications
You must be signed in to change notification settings - Fork 4
fix: propagate child process exit code #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,12 +67,12 @@ func (b *LandJail) Run(ctx context.Context) error { | |
| ctx, cancel := context.WithCancel(ctx) | ||
| defer cancel() | ||
|
|
||
| // childErr receives the result of RunChildProcess so we can | ||
| // propagate the child's exit code to our caller. | ||
| childErr := make(chan error, 1) | ||
| go func() { | ||
| defer cancel() | ||
| err := b.RunChildProcess(os.Args) | ||
| if err != nil { | ||
| b.logger.Error("Failed to run child process", "error", err) | ||
| } | ||
| childErr <- b.RunChildProcess(os.Args) | ||
| }() | ||
|
|
||
| // Setup signal handling BEFORE any setup | ||
|
|
@@ -89,7 +89,16 @@ func (b *LandJail) Run(ctx context.Context) error { | |
| b.logger.Info("Command completed, shutting down...") | ||
| } | ||
|
|
||
| return nil | ||
| // Drain the child result if available. In the ctx.Done path the | ||
| // error is already buffered. In the signal path the child may still | ||
| // be running; return nil so deferred cleanup (iptables, proxy) can | ||
| // proceed before the process exits. | ||
| select { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Asking for clarity: why do we need a second select here instead of a three case select above? select {
case sig := <-sigChan:
// ...
case err := <-childErr:
// ...
case <-ctx.Done():
// ...
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the child finishes, the goroutine sends on childErr AND calls defer Two selects avoid that: first one waits for signal or context cancellation, second one (non-blocking) drains the child result. In the |
||
| case err := <-childErr: | ||
| return err | ||
| default: | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func (b *LandJail) RunChildProcess(command []string) error { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a convenient solution, but it makes me nervous to have an os.Exit that is more than a single level of indirection from the entrypoint like this. Looking at this bit of code here, we don't how what cleanup would have happened on the return path between here and the entry point.
I think the proper solution is to do the error checking near the entry point both here and in the coder subcommand.
In practice, the risk is low and its easy to patch later, so I don't think this blocks the PR. Its worth a mention though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed it's not ideal. The problem is the embedded mode (
coder boundary ...), we don't control coder's entrypoint, and serpent wraps our returned error inRunCommandError, so coder'smain()just doesos.Exit(1)losing the actual code.To do it "properly" we'd need changes in coder/coder or serpent. This is a conscious tradeoff: all cleanup (proxy, iptables) already ran inside Run() via defers before the error returns, so the
os.Exitis safe.Let me know your thoughts!