@@ -19,6 +19,7 @@ import (
1919 "net/url"
2020 "os"
2121 "os/exec"
22+ "os/signal"
2223 "path/filepath"
2324 "regexp"
2425 "sort"
@@ -1607,10 +1608,11 @@ func (c *cliState) doRunStatements(nextState cliStateEnum) cliStateEnum {
16071608 }
16081609
16091610 // Now run the statement/query.
1610- c .exitErr = c .sqlExecCtx .RunQueryAndFormatResults (
1611- context .Background (),
1612- c .conn , c .iCtx .stdout , c .iCtx .stderr ,
1613- clisqlclient .MakeQuery (c .concatLines ))
1611+ c .exitErr = c .runWithInterruptableCtx (func (ctx context.Context ) error {
1612+ return c .sqlExecCtx .RunQueryAndFormatResults (ctx ,
1613+ c .conn , c .iCtx .stdout , c .iCtx .stderr ,
1614+ clisqlclient .MakeQuery (c .concatLines ))
1615+ })
16141616 if c .exitErr != nil {
16151617 if ! c .singleStatement {
16161618 clierror .OutputError (c .iCtx .stderr , c .exitErr , true /*showSeverity*/ , false /*verbose*/ )
@@ -1640,10 +1642,11 @@ func (c *cliState) doRunStatements(nextState cliStateEnum) cliStateEnum {
16401642 if strings .Contains (c .iCtx .autoTrace , "kv" ) {
16411643 traceType = "kv"
16421644 }
1643- if err := c .sqlExecCtx .RunQueryAndFormatResults (
1644- context .Background (),
1645- c .conn , c .iCtx .stdout , c .iCtx .stderr ,
1646- clisqlclient .MakeQuery (fmt .Sprintf ("SHOW %s TRACE FOR SESSION" , traceType ))); err != nil {
1645+ if err := c .runWithInterruptableCtx (func (ctx context.Context ) error {
1646+ return c .sqlExecCtx .RunQueryAndFormatResults (ctx ,
1647+ c .conn , c .iCtx .stdout , c .iCtx .stderr ,
1648+ clisqlclient .MakeQuery (fmt .Sprintf ("SHOW %s TRACE FOR SESSION" , traceType )))
1649+ }); err != nil {
16471650 clierror .OutputError (c .iCtx .stderr , err , true /*showSeverity*/ , false /*verbose*/ )
16481651 if c .exitErr == nil {
16491652 // Both the query and SET tracing had encountered no error
@@ -1705,6 +1708,9 @@ func NewShell(
17051708
17061709// RunInteractive implements the Shell interface.
17071710func (c * cliState ) RunInteractive (cmdIn , cmdOut , cmdErr * os.File ) (exitErr error ) {
1711+ finalFn := c .maybeHandleInterrupt ()
1712+ defer finalFn ()
1713+
17081714 return c .doRunShell (cliStart , cmdIn , cmdOut , cmdErr )
17091715}
17101716
@@ -1986,3 +1992,85 @@ func (c *cliState) serverSideParse(sql string) (helpText string, err error) {
19861992 }
19871993 return "" , nil
19881994}
1995+
1996+ func (c * cliState ) maybeHandleInterrupt () func () {
1997+ if ! c .cliCtx .IsInteractive {
1998+ return func () {}
1999+ }
2000+ intCh := make (chan os.Signal , 1 )
2001+ signal .Notify (intCh , os .Interrupt )
2002+ ctx , cancel := context .WithCancel (context .Background ())
2003+ go func () {
2004+ for {
2005+ select {
2006+ case <- intCh :
2007+ c .iCtx .mu .Lock ()
2008+ cancelFn , doneCh := c .iCtx .mu .cancelFn , c .iCtx .mu .doneCh
2009+ c .iCtx .mu .Unlock ()
2010+ if cancelFn == nil {
2011+ // No query currently executing; nothing to do.
2012+ continue
2013+ }
2014+
2015+ fmt .Fprintf (c .iCtx .stderr , "\n attempting to cancel query...\n " )
2016+ // Cancel the query's context, which should make the driver
2017+ // send a cancellation message.
2018+ cancelFn ()
2019+
2020+ // Now wait for the shell to process the cancellation.
2021+ //
2022+ // If it takes too long (e.g. server has become unresponsive,
2023+ // or we're connected to a pre-22.1 server which does not
2024+ // support cancellation), fall back to the previous behavior
2025+ // which is to interrupt the shell altogether.
2026+ tooLongTimer := time .After (3 * time .Second )
2027+ wait:
2028+ for {
2029+ select {
2030+ case <- doneCh :
2031+ break wait
2032+ case <- tooLongTimer :
2033+ fmt .Fprintln (c .iCtx .stderr , "server does not respond to query cancellation; a second interrupt will stop the shell." )
2034+ signal .Reset (os .Interrupt )
2035+ }
2036+ }
2037+ // Re-arm the signal handler.
2038+ signal .Notify (intCh , os .Interrupt )
2039+
2040+ case <- ctx .Done ():
2041+ // Shell is terminating.
2042+ return
2043+ }
2044+ }
2045+ }()
2046+ return cancel
2047+ }
2048+
2049+ func (c * cliState ) runWithInterruptableCtx (fn func (ctx context.Context ) error ) error {
2050+ if ! c .cliCtx .IsInteractive {
2051+ return fn (context .Background ())
2052+ }
2053+ // The cancellation function can be used by the Ctrl+C handler
2054+ // to cancel this query.
2055+ ctx , cancel := context .WithCancel (context .Background ())
2056+ // doneCh will be used on the return path to teach the Ctrl+C
2057+ // handler that the query has been cancelled successfully.
2058+ doneCh := make (chan struct {})
2059+ defer func () { close (doneCh ) }()
2060+
2061+ // Inform the Ctrl+C handler that this query is executing.
2062+ c .iCtx .mu .Lock ()
2063+ c .iCtx .mu .cancelFn = cancel
2064+ c .iCtx .mu .doneCh = doneCh
2065+ c .iCtx .mu .Unlock ()
2066+ defer func () {
2067+ c .iCtx .mu .Lock ()
2068+ c .iCtx .mu .cancelFn = nil
2069+ c .iCtx .mu .doneCh = nil
2070+ c .iCtx .mu .Unlock ()
2071+ }()
2072+
2073+ // Now run the query.
2074+ err := fn (ctx )
2075+ return err
2076+ }
0 commit comments