Skip to content

Commit

Permalink
Added handling of SIGHUP and SIGTERM to handle a clean exit: and avoi…
Browse files Browse the repository at this point in the history
…d leaking gopls daemons
  • Loading branch information
janpfeifer committed Apr 6, 2024
1 parent 3f10df7 commit 00adde7
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 34 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
the notebook.
* Added `plotly.AppendFig` that allows plotting to a transient area, or anywhere in the page.
* Several minor fixes, see #106
* Added handling of SIGHUP and SIGTERM to handle a clean exit: and avoid leaking `gopls` daemons.

## 0.9.6, 2024/02/18

Expand Down
52 changes: 18 additions & 34 deletions internal/kernel/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/go-zeromq/zmq4"
"github.com/janpfeifer/gonb/common"
"github.com/janpfeifer/gonb/gonbui/protocol"
"github.com/janpfeifer/must"
Expand All @@ -23,9 +24,6 @@ import (
"regexp"
"sync"
"sync/atomic"
"syscall"

"github.com/go-zeromq/zmq4"
)

var (
Expand Down Expand Up @@ -102,8 +100,8 @@ type Kernel struct {
// ExecCounter is incremented each time we run user code in the notebook.
ExecCounter int

// Channel where interruption is received.
sigintC, sighupC chan os.Signal
// Channel where signals are received.
signalsChan chan os.Signal

// Interrupted indicates whether shell currently being executed was Interrupted.
Interrupted atomic.Bool
Expand Down Expand Up @@ -175,44 +173,30 @@ func (k *Kernel) Stop() {
// So instead of the kernel dying, it will recover, and where appropriate
// interrupt other subprocesses it may have spawned.
func (k *Kernel) HandleInterrupt() {
// Handle Sigint: Control+C, Jupyter uses it to ask for the kernel
// Handle Sigint (os.Interrupt): Control+C, Jupyter uses it to ask for the kernel
// to interrupt execution of a program.
if k.sigintC == nil {
k.sigintC = make(chan os.Signal, 1)
signal.Notify(k.sigintC, os.Interrupt)
// All other signals captured are assumed to mean that the kernel should exit.
if k.signalsChan == nil {
k.signalsChan = make(chan os.Signal, 1)
signal.Notify(k.signalsChan, CaptureSignals...) // Signals we are interested in.
go func() {
// At exit reset notification.
defer func() {
signal.Reset(os.Interrupt)
k.sigintC = nil
k.signalsChan = nil
}()
for {
select {
case <-k.sigintC:
case sig := <-k.signalsChan:
k.Interrupted.Store(true)
klog.Infof("INTERRUPT received.")
case <-k.stop:
return // kernel stopped.
}
}
}()
}

// Handle Sighup: sent when the parent process dies.
if k.sighupC == nil {
k.sighupC = make(chan os.Signal, 1)
signal.Notify(k.sighupC, syscall.SIGHUP)
go func() {
// At exit reset notification.
defer func() {
signal.Reset(syscall.SIGHUP)
k.sighupC = nil
}()
for {
select {
case <-k.sighupC:
klog.Infof("SIGHUP received, stopping kernel.")
k.Stop()
sigName := sig.String()
klog.Infof("Signal %s received.", sigName)
switch sigName {
case "terminated", "hangup":
// These signals should stop the kernel.
klog.Errorf("Signal %s triggers kernel stop.", sigName)
k.Stop()
}
case <-k.stop:
return // kernel stopped.
}
Expand Down
16 changes: 16 additions & 0 deletions internal/kernel/signals_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build linux || darwin

package kernel

import (
"os"
"syscall"
)

// CaptureSignals list all signals to be captured: except of `os.Interrupt` (Control+C), all others trigger
// a clean exit of GoNB kernel.
//
// Notice `os.Interrupt` is used by Jupyter to signal to interrupt the execution of the current cell.
var CaptureSignals = []os.Signal{
os.Interrupt, syscall.SIGHUP, syscall.SIGTERM,
}
13 changes: 13 additions & 0 deletions internal/kernel/signals_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !(linux || darwin)

package kernel

import (
"os"
)

// CaptureSignals list all signals to be captured: except of `os.Interrupt` (Control+C), all others trigger
// a clean exit of GoNB kernel.
//
// Notice `os.Interrupt` is used by Jupyter to signal to interrupt the execution of the current cell.
var CaptureSignals = []os.Signal{os.Interrupt}

0 comments on commit 00adde7

Please sign in to comment.