From 032cd8e01c73fd4d1b8df7deac7bf44811448001 Mon Sep 17 00:00:00 2001 From: Wallace Breza Date: Wed, 5 Mar 2025 14:56:11 -0800 Subject: [PATCH 1/2] Updates how interrupts are handled --- cli/azd/main.go | 24 ++++++++++++++++++++++++ cli/azd/pkg/input/console.go | 31 ++++++++++++++++--------------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/cli/azd/main.go b/cli/azd/main.go index afcf29e0c58..de40e45c331 100644 --- a/cli/azd/main.go +++ b/cli/azd/main.go @@ -16,10 +16,12 @@ import ( "net/http" "os" "os/exec" + "os/signal" "path/filepath" "runtime" "strconv" "strings" + "syscall" "time" azcorelog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" @@ -39,6 +41,7 @@ import ( func main() { ctx := context.Background() + ctx = withInterruptContext(ctx) restoreColorMode := colorable.EnableColorsStdout(nil) defer restoreColorMode() @@ -381,3 +384,24 @@ func startBackgroundUploadProcess() error { err = cmd.Start() return err } + +// withInterruptContext creates a new context that is cancelled when the user +// sends an interrupt signal (Ctrl+C) or a SIGTERM signal to the process. +func withInterruptContext(ctx context.Context) context.Context { + ctx, cancel := context.WithCancel(ctx) + + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) + go func() { + <-signalChan + log.Println("received interrupt signal, cancelling context") + cancel() + + time.Sleep(1 * time.Second) + err := fmt.Errorf("Cancelled by user: %w", ctx.Err()) + fmt.Println(output.WithErrorFormat("ERROR: %s", err.Error())) + os.Exit(1) + }() + + return ctx +} diff --git a/cli/azd/pkg/input/console.go b/cli/azd/pkg/input/console.go index 8f115ae89e0..466e88e59bb 100644 --- a/cli/azd/pkg/input/console.go +++ b/cli/azd/pkg/input/console.go @@ -170,6 +170,8 @@ type AskerConsole struct { // holds the last 2 bytes written by message or messageUX. This is used to detect when there is already an empty // line (\n\n) last2Byte [2]byte + + spinnerStopChan chan struct{} } type ConsoleOptions struct { @@ -406,7 +408,20 @@ func (c *AskerConsole) ShowSpinner(ctx context.Context, title string, format Spi // While it is indeed safe to call Start regardless of whether the spinner is running, // calling Start may result in an additional line of output being written in non-tty scenarios _ = c.spinner.Start() + + go func() { + c.spinnerStopChan = make(chan struct{}, 1) + + select { + case <-ctx.Done(): + case <-c.spinnerStopChan: + } + + _ = c.spinner.Stop() + return + }() } + c.spinnerLineMu.Unlock() } @@ -468,7 +483,7 @@ func (c *AskerConsole) StopSpinner(ctx context.Context, lastMessage string, form lastMessage = c.getStopChar(format) + " " + lastMessage } - _ = c.spinner.Stop() + c.spinnerStopChan <- struct{}{} if lastMessage != "" { // Avoid using StopMessage() as it may result in an extra Message line print in non-tty scenarios fmt.Fprintln(c.writer, lastMessage) @@ -938,19 +953,6 @@ func watchTerminalResize(c *AskerConsole) { } } -func watchTerminalInterrupt(c *AskerConsole) { - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, os.Interrupt) - go func() { - <-signalChan - - // unhide the cursor if applicable - _ = c.spinner.Stop() - - os.Exit(1) - }() -} - // Writers that back the underlying console. type Writers struct { // The writer to write output to. @@ -1015,7 +1017,6 @@ func NewConsole( if isTerminal { c.consoleWidth = atomic.NewInt32(consoleWidth()) watchTerminalResize(c) - watchTerminalInterrupt(c) } return c From cff042207add1d49d743b57c53bc7630b8c20ccf Mon Sep 17 00:00:00 2001 From: Wallace Breza Date: Wed, 5 Mar 2025 15:01:28 -0800 Subject: [PATCH 2/2] Minor update --- cli/azd/pkg/input/console.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/azd/pkg/input/console.go b/cli/azd/pkg/input/console.go index 466e88e59bb..cdc9e0f3cc2 100644 --- a/cli/azd/pkg/input/console.go +++ b/cli/azd/pkg/input/console.go @@ -418,7 +418,6 @@ func (c *AskerConsole) ShowSpinner(ctx context.Context, title string, format Spi } _ = c.spinner.Stop() - return }() }