diff --git a/cmd/cmd.go b/cmd/cmd.go index 379609d294..a6e5d8fcfe 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,4 +1,5 @@ // Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2026 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT // Package cmd provides subcommands to the gitea binary - such as "web" or @@ -89,26 +90,9 @@ If this is the intended configuration file complete the [database] section.`, se return nil } +// installSignals returns a context that's cancelled on the SIGINT and SIGTERM signals or if the passed ctx is cancelled. func installSignals(ctx context.Context) (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(ctx) - go func() { - // install notify - signalChannel := make(chan os.Signal, 1) - - signal.Notify( - signalChannel, - syscall.SIGINT, - syscall.SIGTERM, - ) - select { - case <-signalChannel: - case <-ctx.Done(): - } - cancel() - signal.Reset() - }() - - return ctx, cancel + return signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) } func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000000..8e66cbdba1 --- /dev/null +++ b/cmd/cmd_test.go @@ -0,0 +1,39 @@ +// Copyright 2026 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT +package cmd + +import ( + "context" + "fmt" + "runtime" + "syscall" + "testing" + "time" +) + +func Test_installSignals(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skipf("Windows does not terminate in an awaitable manner") + return + } + + for _, s := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} { + t.Run(fmt.Sprintf("Context is terminated on %s", s), func(t *testing.T) { + // Register the signal handler. context.Background() is chosen deliberately, + // because unlike t.Context(), we can be sure that it's not cancelled by a + // different handler. + ctx, cancel := installSignals(context.Background()) + t.Cleanup(cancel) + + // Send the signal in the background. + go syscall.Kill(syscall.Getpid(), s) + + select { + case <-time.Tick(time.Second * 10): + t.Fatalf("Context not cancelled via signal after 10 seconds") + case <-ctx.Done(): + t.Logf("Context was cancelled") + } + }) + } +}