From 6c87a1e4db676f85e0946cc84389f9ebca95298a Mon Sep 17 00:00:00 2001 From: Jan Pfeifer Date: Sun, 7 Apr 2024 12:06:14 +0200 Subject: [PATCH] Set interrupt_mode to message and handle interrupt_request. --- docs/CHANGELOG.md | 5 +++++ internal/dispatcher/dispatcher.go | 10 ++++++++++ internal/kernel/install.go | 21 +++++++++++++-------- internal/kernel/kernel.go | 8 ++++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index de9b773..3f8fc71 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,10 @@ # GoNB Changelog +## Next + +* [`interrupt_mode`] set to `message`, as opposed to having a `SIGINT`. Works both in JupyterLab and VSCode. +* Interrupt all cell executions at `shutdown_request`. + ## 0.10.0, 2024/04/07 Improvements on Plotly, VSCode support, interrupt handling and several minor fixes. * Added special cell commands ("magic"): diff --git a/internal/dispatcher/dispatcher.go b/internal/dispatcher/dispatcher.go index 5f47eef..6309e2b 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/dispatcher/dispatcher.go @@ -137,6 +137,15 @@ func handleShellMsg(msg kernel.Message, goExec *goexec.State) (err error) { err = errors.WithMessagef(err, "replying 'shutdown_request'") } + case "interrupt_request": + // Interrupt current cell being executed if any. + klog.V(2).Infof("Received interrupt_request.") + msg.Kernel().CallInterruptSubscribers() + replyContent := make(map[string]any) + replyContent["status"] = "ok" + err = msg.Reply("interrupt_reply", replyContent) + klog.V(2).Infof("Replied with interrupt_reply.") + default: // Log, ignore, and hope for the best. klog.Infof("Unhandled shell-socket message %q", msg.ComposedMsg().Header.MsgType) @@ -226,6 +235,7 @@ func handleBusyMessage(msg kernel.Message, goExec *goexec.State) (err error) { // handleShutdownRequest sends a "shutdown" message. func handleShutdownRequest(msg kernel.Message, goExec *goexec.State) error { klog.Info("Shutting down in response to shutdown_request") + msg.Kernel().CallInterruptSubscribers() // Interrupt current runs. content := msg.ComposedMsg().Content.(map[string]any) replyContent := make(map[string]any) diff --git a/internal/kernel/install.go b/internal/kernel/install.go index 7d0d2ce..cfb2d42 100644 --- a/internal/kernel/install.go +++ b/internal/kernel/install.go @@ -25,11 +25,13 @@ var logoSVG []byte // jupyterKernelConfig is the Jupyter configuration to be // converted to a `kernel.json` file under `~/.local/share/jupyter/kernels/gonb` // (or `${HOME}/Library/Jupyter/kernels/` in Macs) +// See details in: https://jupyter-client.readthedocs.io/en/latest/kernels.html#kernelspecs type jupyterKernelConfig struct { - Argv []string `json:"argv"` - DisplayName string `json:"display_name"` - Language string `json:"language"` - Env map[string]string `json:"env"` + Argv []string `json:"argv"` + DisplayName string `json:"display_name"` + Language string `json:"language"` + InterruptMode string `json:"interrupt_mode"` + Env map[string]string `json:"env"` } // Install gonb in users local Jupyter configuration, making it available. It assumes @@ -40,16 +42,19 @@ type jupyterKernelConfig struct { // the kernel configuration, and that copy is used. // // If forceDeps is true, installation will succeed even with missing dependencies. +// +// Documentation: https://jupyter-client.readthedocs.io/en/latest/kernels.html#kernelspecs func Install(extraArgs []string, forceDeps, forceCopy bool) error { gonbPath, err := os.Executable() if err != nil { return errors.Wrapf(err, "Failed to find path to GoNB binary") } config := jupyterKernelConfig{ - Argv: []string{gonbPath, "--kernel", "{connection_file}"}, - DisplayName: "Go (gonb)", - Language: "go", - Env: make(map[string]string), + Argv: []string{gonbPath, "--kernel", "{connection_file}"}, + DisplayName: "Go (gonb)", + Language: "go", + InterruptMode: "message", // "message" (a `interrupt_request` is sent) or "signal" (using SIGINT signal) + Env: make(map[string]string), } if len(extraArgs) > 0 { config.Argv = append(config.Argv, extraArgs...) diff --git a/internal/kernel/kernel.go b/internal/kernel/kernel.go index 471952c..0fb1529 100644 --- a/internal/kernel/kernel.go +++ b/internal/kernel/kernel.go @@ -194,7 +194,7 @@ func (k *Kernel) HandleInterrupt() { select { case sig := <-k.signalsChan: k.Interrupted.Store(true) - k.callInterruptSubscribers() + k.CallInterruptSubscribers() klog.Infof("Signal %s received.", sig) if sig == os.Interrupt { // Simply interrupt running cells. @@ -245,9 +245,9 @@ func (k *Kernel) UnsubscribeInterrupt(id SubscriptionId) { } } -// callInterruptSubscribers in a separate goroutine each. -// Meant to be called when JupyterServer sends a kernel interrupt (either a SIGINT, or a message to interrupt). -func (k *Kernel) callInterruptSubscribers() { +// CallInterruptSubscribers in a separate goroutine each. +// Meant to be called when JupyterServer sends a kernel interrupt (either a SIGINT, or a `interrupt_request` message to interrupt). +func (k *Kernel) CallInterruptSubscribers() { k.muSubscriptions.Lock() defer k.muSubscriptions.Unlock()