-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
v2 feature: Allow registering ctrl-c with the app #945
Comments
Hi @ansrivas, thanks for the well written issue! I'm not quite sure if this is a bug, or a documentation issue? 🔍 I'll add both labels for now. I'm also marking this as |
Is there any reason to label this issue as feature? I think this should be labeled as a bug. Because almost all foreground processes that received an interrupt signal exits right away or do clean up and then exit. |
I don't think it's a bug. Killing the application without giving users code a chance to clean up would definitely be a bug. What if the user wants to roll back a transaction or something else on interrupt? At this point we require the users of the library handle termination themselves, as we are not sure what they want to do when that happens. The purpose of this library is mostly parse arguments and direct them to functions, not the application lifecycle management. |
Yes, the purpose of this library is mostly parse arguments and direct them to functions, not the application life cycle management. But this version right now is handling life cycle management by intercepting interrupt signal and by doing that it's doing something outside of it's scope of purpose. |
The v2 version does not do that. I guess you are arguing that it should stay that way? |
@AudriusButkevicius there's either some form of life cycle management going on, or a bug in the behavior right now. Going for the most minimal example based on the issue description: package main
import (
"fmt"
"os"
"time"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Action: func(c *cli.Context) error {
fmt.Println("starting...")
time.Sleep(time.Duration(time.Second * 10))
fmt.Println("done")
return nil
},
}
// Pressing ctrl+C here does NOT interrupt execution
app.Run(os.Args)
// Pressing ctrl+C here DOES interrupt execution
app.Action(nil)
} I haven't dug into exactly why that's the case, but it is not what I would have expected the default behavior to be. |
Looks like the behavior was introduced in #840 Specifically: Lines 38 to 47 in 2dc008d
So apparently we are doing lifecycle management by default. For certain code paths, including While #953 may be something we're interested in, I think the real resolution to this issue is to use the background context by default, and let users who want to manage signals themselves do it manually: if c.Context == nil {
- ctx, cancel := context.WithCancel(context.Background())
- go func() {
- defer cancel()
- sigs := make(chan os.Signal, 1)
- signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
- <-sigs
- }()
- c.Context = ctx
+ c.Context = context.Background()
} |
☝️ FYI @asahasrabuddhe |
☝️ FYI @marwan-at-work too |
So the first problem is that you are calling action, you should not do that, as thats a struct member used for registering the call back. You calling it is essentially just calling any go function and not related to the library in any way. I am not sure what go's default signal haning behaviour is, but you seem to suggest there is some sort of handler. I guess I did not expect that, and if there is already something, perhaps we should leave it as it is by default but provide additional means in the library to get context cancelled etc when this happens, but don't do that out of the box, as there is some niceness in that from a perspective of a library. |
Sorry if that wasn't clear—Calling
From the docs:
So there is a default behavior, and it is overridden by calling
I think this is the only reasonable path forward. Reference: https://golang.org/pkg/os/signal/#hdr-Default_behavior_of_signals_in_Go_programs |
I think then we should drop the PR I am working on and instead remove the If everyone agrees, please indicate by a 👍 on this post so that I can re-purpose the PR and make the changes. |
@asahasrabuddhe @lynncyrin Thanks for tagging me in on this. The current code is definitely a bug :) But let me expand a little more about how it can be nicely fixed: The It can easily be misleading for many Go developers to see a Context that doesn't propagate the cancellation as expected. That said, I do see how this is a problem but instead of completely removing the code we can easily make it behave like many other CLI tools where the first ctrl+c will issue a cancellation signal, but the second one will issue a force-exit. This can be accomplished by just adding two lines to the codebase in Before:
After: <-sigs
cancel()
<-sigs
os.Exit(0) We can also add some sane defaults, like warning the user that they need to hit ctrl+c again, or maybe just exiting after N milliseconds from the first ctrl+c Furthermore, we can just let the CLI developer customize the behavior by accepting some opts such as "allow the first ctr+c to exit after N milliseconds" or "make the Context cancellation opt-in/out" Something like: &cli.App{
ExitTimeout: time.Second,
ContextCancellation: true,
} Naming to be determined. Why we can't handle signal.Notify on the user side:I say all of this because the problem with letting the user handle Take this app for example package main
import (
"fmt"
"log"
"os"
"time"
"github.com/urfave/cli/v2"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
app := &cli.App{
Name: "boom",
Usage: "make an explosive entrance",
Action: func(c *cli.Context) error {
someAPI.ExpensiveCall(c) // this will never get cancelled, even when we exit the application
return nil
},
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-c
fmt.Println("\r- Ctrl+C pressed in Terminal")
os.Exit(0)
}()
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
} Notice the comment next to the |
One way to solve my code above, is to make a new channel and call Alternate solution:Maybe the Before:
After:
This would be a breaking change so I imagine we can make a new name like This way a user can just do this once:
This can make the user customize their signal cancellation behavior without altering the default behavior of just exiting on crl+c |
I think we need a RunWithContext, or NewAppWithContext. The fact we push down "some" context to the user is cool but if the user has no control over it, it's a bit weird. |
@AudriusButkevicius I like that idea as well 👍 -- I prefer RunWithContext so that users can still describe their API by a struct literal without having to user constructors. Plus, Run() is the thing that creates a context so that's perfect |
I added a PR with @AudriusButkevicius's suggestion. Though I'm happy to change that PR up into any of the other suggestions 👍 |
Checklist
Describe the bug
Thank you for the v2 release.
Expecting the application to handle the
ctrl-c
but it doesn't. So the question then becomes:ctrl-c
handler with the current app? ( I saw in the codeNewContext
but the signal is not getting handled apparently?To reproduce
Run the following snippet and press
ctrl-c
Expected behavior
Expecting the application to stop.
Version of go
go version go1.13.3 linux/amd64
The text was updated successfully, but these errors were encountered: