2017-07-22 16:49:22 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-01-20 17:27:01 +01:00
|
|
|
"context"
|
2017-07-22 16:49:22 -04:00
|
|
|
"encoding/json"
|
2017-09-24 21:28:21 -04:00
|
|
|
"errors"
|
2017-07-22 16:49:22 -04:00
|
|
|
"fmt"
|
2017-09-25 17:55:37 -04:00
|
|
|
"go/ast"
|
2017-08-02 11:39:58 -04:00
|
|
|
"io"
|
2017-07-22 16:49:22 -04:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
2018-06-04 21:55:35 +02:00
|
|
|
"reflect"
|
2017-08-25 23:45:04 -04:00
|
|
|
"runtime"
|
2020-01-09 20:03:21 +01:00
|
|
|
"strings"
|
2017-09-24 21:28:21 -04:00
|
|
|
"sync"
|
|
|
|
"time"
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:47:53 +01:00
|
|
|
"github.com/go-zeromq/zmq4"
|
2020-01-20 17:27:01 +01:00
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
2017-09-25 17:55:37 -04:00
|
|
|
"github.com/cosmos72/gomacro/ast2"
|
2017-08-02 11:39:58 -04:00
|
|
|
"github.com/cosmos72/gomacro/base"
|
2019-06-01 22:30:37 +02:00
|
|
|
basereflect "github.com/cosmos72/gomacro/base/reflect"
|
2018-01-06 19:09:50 -05:00
|
|
|
interp "github.com/cosmos72/gomacro/fast"
|
2019-04-12 22:13:29 +02:00
|
|
|
"github.com/cosmos72/gomacro/xreflect"
|
2019-11-28 22:08:37 +01:00
|
|
|
|
|
|
|
// compile and link files generated in imports/
|
|
|
|
_ "github.com/gopherdata/gophernotes/imports"
|
2017-07-22 16:49:22 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// ExecCounter is incremented each time we run user code in the notebook.
|
|
|
|
var ExecCounter int
|
|
|
|
|
|
|
|
// ConnectionInfo stores the contents of the kernel connection
|
|
|
|
// file created by Jupyter.
|
|
|
|
type ConnectionInfo struct {
|
|
|
|
SignatureScheme string `json:"signature_scheme"`
|
|
|
|
Transport string `json:"transport"`
|
|
|
|
StdinPort int `json:"stdin_port"`
|
|
|
|
ControlPort int `json:"control_port"`
|
|
|
|
IOPubPort int `json:"iopub_port"`
|
|
|
|
HBPort int `json:"hb_port"`
|
|
|
|
ShellPort int `json:"shell_port"`
|
|
|
|
Key string `json:"key"`
|
|
|
|
IP string `json:"ip"`
|
|
|
|
}
|
|
|
|
|
2018-06-02 18:50:13 +02:00
|
|
|
// Socket wraps a zmq socket with a lock which should be used to control write access.
|
|
|
|
type Socket struct {
|
2020-01-20 17:47:53 +01:00
|
|
|
Socket zmq4.Socket
|
2018-06-02 18:50:13 +02:00
|
|
|
Lock *sync.Mutex
|
|
|
|
}
|
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
// SocketGroup holds the sockets needed to communicate with the kernel,
|
|
|
|
// and the key for message signing.
|
|
|
|
type SocketGroup struct {
|
2018-06-02 18:50:13 +02:00
|
|
|
ShellSocket Socket
|
|
|
|
ControlSocket Socket
|
|
|
|
StdinSocket Socket
|
|
|
|
IOPubSocket Socket
|
|
|
|
HBSocket Socket
|
2017-07-22 16:49:22 -04:00
|
|
|
Key []byte
|
|
|
|
}
|
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
// KernelLanguageInfo holds information about the language that this kernel executes code in.
|
2017-08-25 23:45:04 -04:00
|
|
|
type kernelLanguageInfo struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
MIMEType string `json:"mimetype"`
|
|
|
|
FileExtension string `json:"file_extension"`
|
|
|
|
PygmentsLexer string `json:"pygments_lexer"`
|
|
|
|
CodeMirrorMode string `json:"codemirror_mode"`
|
|
|
|
NBConvertExporter string `json:"nbconvert_exporter"`
|
|
|
|
}
|
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
// HelpLink stores data to be displayed in the help menu of the notebook.
|
2017-08-25 23:45:04 -04:00
|
|
|
type helpLink struct {
|
|
|
|
Text string `json:"text"`
|
|
|
|
URL string `json:"url"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// KernelInfo holds information about the igo kernel, for kernel_info_reply messages.
|
|
|
|
type kernelInfo struct {
|
|
|
|
ProtocolVersion string `json:"protocol_version"`
|
|
|
|
Implementation string `json:"implementation"`
|
|
|
|
ImplementationVersion string `json:"implementation_version"`
|
|
|
|
LanguageInfo kernelLanguageInfo `json:"language_info"`
|
|
|
|
Banner string `json:"banner"`
|
|
|
|
HelpLinks []helpLink `json:"help_links"`
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// shutdownReply encodes a boolean indication of shutdown/restart.
|
2017-07-22 16:49:22 -04:00
|
|
|
type shutdownReply struct {
|
|
|
|
Restart bool `json:"restart"`
|
|
|
|
}
|
|
|
|
|
2017-09-12 11:12:59 -04:00
|
|
|
const (
|
|
|
|
kernelStarting = "starting"
|
|
|
|
kernelBusy = "busy"
|
|
|
|
kernelIdle = "idle"
|
|
|
|
)
|
|
|
|
|
2018-06-02 18:50:13 +02:00
|
|
|
// RunWithSocket invokes the `run` function after acquiring the `Socket.Lock` and releases the lock when done.
|
2020-01-20 17:47:53 +01:00
|
|
|
func (s *Socket) RunWithSocket(run func(socket zmq4.Socket) error) error {
|
2018-06-02 18:50:13 +02:00
|
|
|
s.Lock.Lock()
|
|
|
|
defer s.Lock.Unlock()
|
|
|
|
return run(s.Socket)
|
|
|
|
}
|
|
|
|
|
2019-04-12 22:13:29 +02:00
|
|
|
type Kernel struct {
|
|
|
|
ir *interp.Interp
|
|
|
|
display *interp.Import
|
2019-04-13 22:57:40 +02:00
|
|
|
// map name -> HTMLer, JSONer, Renderer...
|
2019-04-12 22:13:29 +02:00
|
|
|
// used to convert interpreted types to one of these interfaces
|
2019-04-13 22:57:40 +02:00
|
|
|
render map[string]xreflect.Type
|
2019-04-12 22:13:29 +02:00
|
|
|
}
|
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
// runKernel is the main entry point to start the kernel.
|
|
|
|
func runKernel(connectionFile string) {
|
|
|
|
|
2018-01-06 19:09:50 -05:00
|
|
|
// Create a new interpreter for evaluating notebook code.
|
|
|
|
ir := interp.New()
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2017-10-20 23:21:48 -04:00
|
|
|
// Throw out the error/warning messages that gomacro outputs writes to these streams.
|
2018-01-06 19:09:50 -05:00
|
|
|
ir.Comp.Stdout = ioutil.Discard
|
|
|
|
ir.Comp.Stderr = ioutil.Discard
|
2017-10-20 23:21:48 -04:00
|
|
|
|
2018-06-02 17:55:12 +02:00
|
|
|
// Inject the "display" package to render HTML, JSON, PNG, JPEG, SVG... from interpreted code
|
2018-06-04 21:55:35 +02:00
|
|
|
// maybe a dot-import is easier to use?
|
2019-04-12 22:13:29 +02:00
|
|
|
display, err := ir.Comp.ImportPackageOrError("display", "display")
|
2018-06-02 17:55:12 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
}
|
|
|
|
|
2018-06-04 21:55:35 +02:00
|
|
|
// Inject the stub "Display" function. declare a variable
|
|
|
|
// instead of a function, because we want to later change
|
|
|
|
// its value to the closure that holds a reference to msgReceipt
|
|
|
|
ir.DeclVar("Display", nil, stubDisplay)
|
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
// Parse the connection info.
|
|
|
|
var connInfo ConnectionInfo
|
|
|
|
|
|
|
|
connData, err := ioutil.ReadFile(connectionFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = json.Unmarshal(connData, &connInfo); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up the ZMQ sockets through which the kernel will communicate.
|
|
|
|
sockets, err := prepareSockets(connInfo)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// TODO connect all channel handlers to a WaitGroup to ensure shutdown before returning from runKernel.
|
2017-09-24 17:13:50 -04:00
|
|
|
|
2017-09-30 01:51:15 -04:00
|
|
|
// Start up the heartbeat handler.
|
2017-10-31 21:37:12 -04:00
|
|
|
startHeartbeat(sockets.HBSocket, &sync.WaitGroup{})
|
|
|
|
|
|
|
|
// TODO gracefully shutdown the heartbeat handler on kernel shutdown by closing the chan returned by startHeartbeat.
|
2017-09-24 17:13:50 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
type msgType struct {
|
2020-01-20 17:47:53 +01:00
|
|
|
Msg zmq4.Msg
|
2020-01-20 17:27:01 +01:00
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
shell = make(chan msgType)
|
|
|
|
stdin = make(chan msgType)
|
|
|
|
ctl = make(chan msgType)
|
|
|
|
quit = make(chan int)
|
|
|
|
)
|
|
|
|
|
|
|
|
defer close(quit)
|
2020-01-20 17:47:53 +01:00
|
|
|
poll := func(msgs chan msgType, sck zmq4.Socket) {
|
2020-01-20 17:27:01 +01:00
|
|
|
defer close(msgs)
|
|
|
|
for {
|
|
|
|
msg, err := sck.Recv()
|
|
|
|
select {
|
|
|
|
case msgs <- msgType{Msg: msg, Err: err}:
|
|
|
|
case <-quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
go poll(shell, sockets.ShellSocket.Socket)
|
|
|
|
go poll(stdin, sockets.StdinSocket.Socket)
|
|
|
|
go poll(ctl, sockets.ControlSocket.Socket)
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2019-04-12 22:13:29 +02:00
|
|
|
kernel := Kernel{
|
|
|
|
ir,
|
|
|
|
display,
|
|
|
|
nil,
|
|
|
|
}
|
|
|
|
kernel.initRenderers()
|
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
// Start a message receiving loop.
|
|
|
|
for {
|
2020-01-20 17:27:01 +01:00
|
|
|
select {
|
|
|
|
case v := <-shell:
|
2017-07-22 16:49:22 -04:00
|
|
|
// Handle shell messages.
|
2020-01-20 17:27:01 +01:00
|
|
|
if v.Err != nil {
|
|
|
|
log.Println(v.Err)
|
|
|
|
continue
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
msg, ids, err := WireMsgToComposedMsg(v.Msg.Frames, sockets.Key)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
kernel.handleShellMsg(msgReceipt{msg, ids, sockets})
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
case <-stdin:
|
|
|
|
// TODO Handle stdin socket.
|
|
|
|
continue
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
case v := <-ctl:
|
|
|
|
if v.Err != nil {
|
|
|
|
log.Println(v.Err)
|
|
|
|
return
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
msg, ids, err := WireMsgToComposedMsg(v.Msg.Frames, sockets.Key)
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return
|
2017-07-22 16:49:22 -04:00
|
|
|
}
|
2020-01-20 17:27:01 +01:00
|
|
|
|
|
|
|
kernel.handleShellMsg(msgReceipt{msg, ids, sockets})
|
2017-07-22 16:49:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepareSockets sets up the ZMQ sockets through which the kernel
|
|
|
|
// will communicate.
|
|
|
|
func prepareSockets(connInfo ConnectionInfo) (SocketGroup, error) {
|
|
|
|
// Initialize the socket group.
|
2020-01-20 17:27:01 +01:00
|
|
|
var (
|
|
|
|
sg SocketGroup
|
|
|
|
err error
|
|
|
|
ctx = context.Background()
|
|
|
|
)
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// Create the shell socket, a request-reply socket that may receive messages from multiple frontend for
|
|
|
|
// code execution, introspection, auto-completion, etc.
|
2020-01-20 17:47:53 +01:00
|
|
|
sg.ShellSocket.Socket = zmq4.NewRouter(ctx)
|
2018-06-02 18:50:13 +02:00
|
|
|
sg.ShellSocket.Lock = &sync.Mutex{}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// Create the control socket. This socket is a duplicate of the shell socket where messages on this channel
|
|
|
|
// should jump ahead of queued messages on the shell socket.
|
2020-01-20 17:47:53 +01:00
|
|
|
sg.ControlSocket.Socket = zmq4.NewRouter(ctx)
|
2018-06-02 18:50:13 +02:00
|
|
|
sg.ControlSocket.Lock = &sync.Mutex{}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// Create the stdin socket, a request-reply socket used to request user input from a front-end. This is analogous
|
|
|
|
// to a standard input stream.
|
2020-01-20 17:47:53 +01:00
|
|
|
sg.StdinSocket.Socket = zmq4.NewRouter(ctx)
|
2018-06-02 18:50:13 +02:00
|
|
|
sg.StdinSocket.Lock = &sync.Mutex{}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// Create the iopub socket, a publisher for broadcasting data like stdout/stderr output, displaying execution
|
|
|
|
// results or errors, kernel status, etc. to connected subscribers.
|
2020-01-20 17:47:53 +01:00
|
|
|
sg.IOPubSocket.Socket = zmq4.NewPub(ctx)
|
2018-06-02 18:50:13 +02:00
|
|
|
sg.IOPubSocket.Lock = &sync.Mutex{}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// Create the heartbeat socket, a request-reply socket that only allows alternating recv-send (request-reply)
|
|
|
|
// calls. It should echo the byte strings it receives to let the requester know the kernel is still alive.
|
2020-01-20 17:47:53 +01:00
|
|
|
sg.HBSocket.Socket = zmq4.NewRep(ctx)
|
2018-06-02 18:50:13 +02:00
|
|
|
sg.HBSocket.Lock = &sync.Mutex{}
|
2017-09-24 17:13:50 -04:00
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
// Bind the sockets.
|
|
|
|
address := fmt.Sprintf("%v://%v:%%v", connInfo.Transport, connInfo.IP)
|
2020-01-20 17:27:01 +01:00
|
|
|
err = sg.ShellSocket.Socket.Listen(fmt.Sprintf(address, connInfo.ShellPort))
|
|
|
|
if err != nil {
|
|
|
|
return sg, xerrors.Errorf("could not listen on shell-socket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = sg.ControlSocket.Socket.Listen(fmt.Sprintf(address, connInfo.ControlPort))
|
|
|
|
if err != nil {
|
|
|
|
return sg, xerrors.Errorf("could not listen on control-socket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = sg.StdinSocket.Socket.Listen(fmt.Sprintf(address, connInfo.StdinPort))
|
|
|
|
if err != nil {
|
|
|
|
return sg, xerrors.Errorf("could not listen on stdin-socket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = sg.IOPubSocket.Socket.Listen(fmt.Sprintf(address, connInfo.IOPubPort))
|
|
|
|
if err != nil {
|
|
|
|
return sg, xerrors.Errorf("could not listen on iopub-socket: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = sg.HBSocket.Socket.Listen(fmt.Sprintf(address, connInfo.HBPort))
|
|
|
|
if err != nil {
|
|
|
|
return sg, xerrors.Errorf("could not listen on hbeat-socket: %w", err)
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
|
|
|
// Set the message signing key.
|
|
|
|
sg.Key = []byte(connInfo.Key)
|
|
|
|
|
|
|
|
return sg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleShellMsg responds to a message on the shell ROUTER socket.
|
2019-04-12 22:13:29 +02:00
|
|
|
func (kernel *Kernel) handleShellMsg(receipt msgReceipt) {
|
2018-03-14 22:21:17 -04:00
|
|
|
// Tell the front-end that the kernel is working and when finished notify the
|
|
|
|
// front-end that the kernel is idle again.
|
|
|
|
if err := receipt.PublishKernelStatus(kernelBusy); err != nil {
|
|
|
|
log.Printf("Error publishing kernel status 'busy': %v\n", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := receipt.PublishKernelStatus(kernelIdle); err != nil {
|
|
|
|
log.Printf("Error publishing kernel status 'idle': %v\n", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-04-12 22:13:29 +02:00
|
|
|
ir := kernel.ir
|
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
switch receipt.Msg.Header.MsgType {
|
|
|
|
case "kernel_info_request":
|
|
|
|
if err := sendKernelInfo(receipt); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2018-06-02 21:31:33 +02:00
|
|
|
case "complete_request":
|
|
|
|
if err := handleCompleteRequest(ir, receipt); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
case "execute_request":
|
2019-04-12 22:13:29 +02:00
|
|
|
if err := kernel.handleExecuteRequest(receipt); err != nil {
|
2017-07-22 16:49:22 -04:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
case "shutdown_request":
|
2017-08-01 17:22:40 -04:00
|
|
|
handleShutdownRequest(receipt)
|
2017-07-22 16:49:22 -04:00
|
|
|
default:
|
|
|
|
log.Println("Unhandled shell message: ", receipt.Msg.Header.MsgType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sendKernelInfo sends a kernel_info_reply message.
|
|
|
|
func sendKernelInfo(receipt msgReceipt) error {
|
2017-08-25 23:45:04 -04:00
|
|
|
return receipt.Reply("kernel_info_reply",
|
|
|
|
kernelInfo{
|
2017-09-12 10:18:28 -04:00
|
|
|
ProtocolVersion: ProtocolVersion,
|
2017-08-25 23:45:04 -04:00
|
|
|
Implementation: "gophernotes",
|
|
|
|
ImplementationVersion: Version,
|
|
|
|
Banner: fmt.Sprintf("Go kernel: gophernotes - v%s", Version),
|
|
|
|
LanguageInfo: kernelLanguageInfo{
|
|
|
|
Name: "go",
|
|
|
|
Version: runtime.Version(),
|
|
|
|
FileExtension: ".go",
|
|
|
|
},
|
|
|
|
HelpLinks: []helpLink{
|
|
|
|
{Text: "Go", URL: "https://golang.org/"},
|
|
|
|
{Text: "gophernotes", URL: "https://github.com/gopherdata/gophernotes"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2017-07-22 16:49:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// handleExecuteRequest runs code from an execute_request method,
|
|
|
|
// and sends the various reply messages.
|
2019-04-12 22:13:29 +02:00
|
|
|
func (kernel *Kernel) handleExecuteRequest(receipt msgReceipt) error {
|
2017-10-31 21:37:12 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
// Extract the data from the request.
|
2017-07-22 16:49:22 -04:00
|
|
|
reqcontent := receipt.Msg.Content.(map[string]interface{})
|
|
|
|
code := reqcontent["code"].(string)
|
|
|
|
silent := reqcontent["silent"].(bool)
|
|
|
|
|
|
|
|
if !silent {
|
|
|
|
ExecCounter++
|
|
|
|
}
|
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
// Prepare the map that will hold the reply content.
|
2017-08-25 23:45:04 -04:00
|
|
|
content := make(map[string]interface{})
|
2017-07-22 16:49:22 -04:00
|
|
|
content["execution_count"] = ExecCounter
|
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
// Tell the front-end what the kernel is about to execute.
|
|
|
|
if err := receipt.PublishExecutionInput(ExecCounter, code); err != nil {
|
|
|
|
log.Printf("Error publishing execution input: %v\n", err)
|
|
|
|
}
|
2017-08-25 23:45:04 -04:00
|
|
|
|
2017-08-04 10:13:18 -04:00
|
|
|
// Redirect the standard out from the REPL.
|
2017-08-04 10:30:31 -04:00
|
|
|
oldStdout := os.Stdout
|
|
|
|
rOut, wOut, err := os.Pipe()
|
2017-08-02 11:39:58 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-08-04 10:30:31 -04:00
|
|
|
os.Stdout = wOut
|
2017-08-02 11:39:58 -04:00
|
|
|
|
2017-08-04 10:13:18 -04:00
|
|
|
// Redirect the standard error from the REPL.
|
2017-09-24 21:28:21 -04:00
|
|
|
oldStderr := os.Stderr
|
2017-08-04 10:13:18 -04:00
|
|
|
rErr, wErr, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-09-24 21:28:21 -04:00
|
|
|
os.Stderr = wErr
|
2017-08-02 11:39:58 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
var writersWG sync.WaitGroup
|
|
|
|
writersWG.Add(2)
|
2017-08-02 11:39:58 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
// Forward all data written to stdout/stderr to the front-end.
|
|
|
|
go func() {
|
|
|
|
defer writersWG.Done()
|
|
|
|
jupyterStdOut := JupyterStreamWriter{StreamStdout, &receipt}
|
|
|
|
io.Copy(&jupyterStdOut, rOut)
|
|
|
|
}()
|
2017-08-02 11:39:58 -04:00
|
|
|
|
|
|
|
go func() {
|
2017-09-24 21:28:21 -04:00
|
|
|
defer writersWG.Done()
|
|
|
|
jupyterStdErr := JupyterStreamWriter{StreamStderr, &receipt}
|
|
|
|
io.Copy(&jupyterStdErr, rErr)
|
2017-08-02 11:39:58 -04:00
|
|
|
}()
|
2017-08-01 17:22:40 -04:00
|
|
|
|
2018-06-04 21:55:35 +02:00
|
|
|
// inject the actual "Display" closure that displays multimedia data in Jupyter
|
2019-04-12 22:13:29 +02:00
|
|
|
ir := kernel.ir
|
2018-06-04 21:55:35 +02:00
|
|
|
displayPlace := ir.ValueOf("Display")
|
|
|
|
displayPlace.Set(reflect.ValueOf(receipt.PublishDisplayData))
|
2018-06-02 17:55:12 +02:00
|
|
|
defer func() {
|
2018-06-04 21:55:35 +02:00
|
|
|
// remove the closure before returning
|
|
|
|
displayPlace.Set(reflect.ValueOf(stubDisplay))
|
2018-06-02 17:55:12 +02:00
|
|
|
}()
|
2017-09-24 21:28:21 -04:00
|
|
|
|
2018-06-02 17:55:12 +02:00
|
|
|
// eval
|
2019-04-12 22:13:29 +02:00
|
|
|
vals, types, executionErr := doEval(ir, code)
|
2017-09-30 01:51:15 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
// Close and restore the streams.
|
2017-08-04 10:30:31 -04:00
|
|
|
wOut.Close()
|
|
|
|
os.Stdout = oldStdout
|
2017-08-04 10:13:18 -04:00
|
|
|
|
|
|
|
wErr.Close()
|
2017-09-24 21:28:21 -04:00
|
|
|
os.Stderr = oldStderr
|
2017-08-04 10:13:18 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
// Wait for the writers to finish forwarding the data.
|
|
|
|
writersWG.Wait()
|
2017-08-25 23:45:04 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
if executionErr == nil {
|
2019-04-11 22:22:37 +02:00
|
|
|
// if the only non-nil value should be auto-rendered graphically, render it
|
2019-04-12 22:13:29 +02:00
|
|
|
data := kernel.autoRenderResults(vals, types)
|
2018-04-07 16:34:50 +02:00
|
|
|
|
2017-07-22 16:49:22 -04:00
|
|
|
content["status"] = "ok"
|
|
|
|
content["user_expressions"] = make(map[string]string)
|
2017-08-01 17:22:40 -04:00
|
|
|
|
2018-06-11 01:51:40 +02:00
|
|
|
if !silent && len(data.Data) != 0 {
|
2017-09-07 20:51:43 -04:00
|
|
|
// Publish the result of the execution.
|
2018-06-11 01:51:40 +02:00
|
|
|
if err := receipt.PublishExecutionResult(ExecCounter, data); err != nil {
|
2017-09-07 20:51:43 -04:00
|
|
|
log.Printf("Error publishing execution result: %v\n", err)
|
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
}
|
2017-09-24 21:28:21 -04:00
|
|
|
} else {
|
2017-08-04 10:13:18 -04:00
|
|
|
content["status"] = "error"
|
|
|
|
content["ename"] = "ERROR"
|
2017-09-24 21:28:21 -04:00
|
|
|
content["evalue"] = executionErr.Error()
|
2017-08-04 10:13:18 -04:00
|
|
|
content["traceback"] = nil
|
2017-08-01 17:22:40 -04:00
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
if err := receipt.PublishExecutionError(executionErr.Error(), []string{executionErr.Error()}); err != nil {
|
2017-09-07 20:51:43 -04:00
|
|
|
log.Printf("Error publishing execution error: %v\n", err)
|
|
|
|
}
|
2017-08-04 10:13:18 -04:00
|
|
|
}
|
2017-07-22 16:49:22 -04:00
|
|
|
|
|
|
|
// Send the output back to the notebook.
|
2017-08-25 23:45:04 -04:00
|
|
|
return receipt.Reply("execute_reply", content)
|
2017-07-22 16:49:22 -04:00
|
|
|
}
|
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
// doEval evaluates the code in the interpreter. This function captures an uncaught panic
|
2017-10-20 23:12:12 -04:00
|
|
|
// as well as the values of the last statement/expression.
|
2019-04-12 22:13:29 +02:00
|
|
|
func doEval(ir *interp.Interp, code string) (val []interface{}, typ []xreflect.Type, err error) {
|
2017-10-31 21:37:12 -04:00
|
|
|
|
2017-09-30 01:51:15 -04:00
|
|
|
// Capture a panic from the evaluation if one occurs and store it in the `err` return parameter.
|
2017-09-24 21:28:21 -04:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
var ok bool
|
|
|
|
if err, ok = r.(error); !ok {
|
|
|
|
err = errors.New(fmt.Sprint(r))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-01-09 20:03:21 +01:00
|
|
|
code = evalSpecialCommands(ir, code)
|
|
|
|
|
2017-09-24 21:28:21 -04:00
|
|
|
// Prepare and perform the multiline evaluation.
|
2018-01-06 19:09:50 -05:00
|
|
|
compiler := ir.Comp
|
2017-10-31 21:37:12 -04:00
|
|
|
|
2017-10-20 23:12:12 -04:00
|
|
|
// Don't show the gomacro prompt.
|
2018-01-06 19:09:50 -05:00
|
|
|
compiler.Options &^= base.OptShowPrompt
|
2017-10-31 21:37:12 -04:00
|
|
|
|
2017-10-20 23:12:12 -04:00
|
|
|
// Don't swallow panics as they are recovered above and handled with a Jupyter `error` message instead.
|
2018-01-06 19:09:50 -05:00
|
|
|
compiler.Options &^= base.OptTrapPanic
|
2017-10-31 21:37:12 -04:00
|
|
|
|
|
|
|
// Reset the error line so that error messages correspond to the lines from the cell.
|
2018-01-06 19:09:50 -05:00
|
|
|
compiler.Line = 0
|
2017-09-24 21:28:21 -04:00
|
|
|
|
2020-01-09 20:03:21 +01:00
|
|
|
// Parse the input code (and don't perform gomacro's macroexpansion).
|
2018-01-06 19:09:50 -05:00
|
|
|
// These may panic but this will be recovered by the deferred recover() above so that the error
|
|
|
|
// may be returned instead.
|
|
|
|
nodes := compiler.ParseBytes([]byte(code))
|
|
|
|
srcAst := ast2.AnyToAst(nodes, "doEval")
|
2017-09-25 17:55:37 -04:00
|
|
|
|
2018-01-06 19:09:50 -05:00
|
|
|
// If there is no srcAst then we must be evaluating nothing. The result must be nil then.
|
|
|
|
if srcAst == nil {
|
2019-04-12 22:13:29 +02:00
|
|
|
return nil, nil, nil
|
2017-09-30 01:51:15 -04:00
|
|
|
}
|
|
|
|
|
2018-01-06 19:09:50 -05:00
|
|
|
// Check if the last node is an expression. If the last node is not an expression then nothing
|
|
|
|
// is returned as a value. For example evaluating a function declaration shouldn't return a value but
|
|
|
|
// just have the side effect of declaring the function.
|
2018-04-07 15:16:20 +02:00
|
|
|
//
|
|
|
|
// This is actually needed only for gomacro classic interpreter
|
|
|
|
// (the fast interpreter already returns values only for expressions)
|
|
|
|
// but retained for compatibility.
|
2017-09-25 17:55:37 -04:00
|
|
|
var srcEndsWithExpr bool
|
2018-01-06 19:09:50 -05:00
|
|
|
if len(nodes) > 0 {
|
2017-09-30 01:51:15 -04:00
|
|
|
_, srcEndsWithExpr = nodes[len(nodes)-1].(ast.Expr)
|
2017-09-24 21:28:21 -04:00
|
|
|
}
|
|
|
|
|
2018-01-06 19:09:50 -05:00
|
|
|
// Compile the ast.
|
|
|
|
compiledSrc := ir.CompileAst(srcAst)
|
|
|
|
|
2017-09-25 17:55:37 -04:00
|
|
|
// Evaluate the code.
|
2019-04-12 22:13:29 +02:00
|
|
|
results, types := ir.RunExpr(compiledSrc)
|
2017-09-25 17:55:37 -04:00
|
|
|
|
2017-09-30 01:51:15 -04:00
|
|
|
// If the source ends with an expression, then the result of the execution is the value of the expression. In the
|
2017-10-31 21:37:12 -04:00
|
|
|
// event that all return values are nil, the result is also nil.
|
2017-09-25 17:55:37 -04:00
|
|
|
if srcEndsWithExpr {
|
2017-09-30 01:51:15 -04:00
|
|
|
|
2017-10-20 23:12:12 -04:00
|
|
|
// Count the number of non-nil values in the output. If they are all nil then the output is skipped.
|
|
|
|
nonNilCount := 0
|
2018-04-07 15:19:55 +02:00
|
|
|
values := make([]interface{}, len(results))
|
|
|
|
for i, result := range results {
|
2019-06-01 22:30:37 +02:00
|
|
|
val := basereflect.Interface(result)
|
2017-09-30 01:51:15 -04:00
|
|
|
if val != nil {
|
2017-10-20 23:12:12 -04:00
|
|
|
nonNilCount++
|
2017-09-25 17:55:37 -04:00
|
|
|
}
|
2018-04-07 15:19:55 +02:00
|
|
|
values[i] = val
|
2017-10-20 23:12:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if nonNilCount > 0 {
|
2019-04-12 22:13:29 +02:00
|
|
|
return values, types, nil
|
2017-09-25 17:55:37 -04:00
|
|
|
}
|
2017-09-24 21:28:21 -04:00
|
|
|
}
|
|
|
|
|
2019-04-12 22:13:29 +02:00
|
|
|
return nil, nil, nil
|
2017-09-24 21:28:21 -04:00
|
|
|
}
|
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
// handleShutdownRequest sends a "shutdown" message.
|
2017-08-01 17:22:40 -04:00
|
|
|
func handleShutdownRequest(receipt msgReceipt) {
|
2017-07-22 16:49:22 -04:00
|
|
|
content := receipt.Msg.Content.(map[string]interface{})
|
|
|
|
restart := content["restart"].(bool)
|
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
reply := shutdownReply{
|
|
|
|
Restart: restart,
|
|
|
|
}
|
2017-08-25 23:45:04 -04:00
|
|
|
|
2017-09-07 20:51:43 -04:00
|
|
|
if err := receipt.Reply("shutdown_reply", reply); err != nil {
|
2017-08-01 17:22:40 -04:00
|
|
|
log.Fatal(err)
|
2017-07-22 16:49:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("Shutting down in response to shutdown_request")
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2017-09-24 17:13:50 -04:00
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
// startHeartbeat starts a go-routine for handling heartbeat ping messages sent over the given `hbSocket`. The `wg`'s
|
|
|
|
// `Done` method is invoked after the thread is completely shutdown. To request a shutdown the returned `shutdown` channel
|
|
|
|
// can be closed.
|
2018-06-02 18:50:13 +02:00
|
|
|
func startHeartbeat(hbSocket Socket, wg *sync.WaitGroup) (shutdown chan struct{}) {
|
2017-10-31 21:37:12 -04:00
|
|
|
quit := make(chan struct{})
|
2017-09-24 17:13:50 -04:00
|
|
|
|
2017-09-30 01:51:15 -04:00
|
|
|
// Start the handler that will echo any received messages back to the sender.
|
2017-09-24 17:13:50 -04:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2017-09-30 01:51:15 -04:00
|
|
|
|
2020-01-20 17:27:01 +01:00
|
|
|
type msgType struct {
|
2020-01-20 17:47:53 +01:00
|
|
|
Msg zmq4.Msg
|
2020-01-20 17:27:01 +01:00
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
msgs := make(chan msgType)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer close(msgs)
|
|
|
|
for {
|
|
|
|
msg, err := hbSocket.Socket.Recv()
|
|
|
|
select {
|
|
|
|
case msgs <- msgType{msg, err}:
|
|
|
|
case <-quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
timeout := time.NewTimer(500 * time.Second)
|
|
|
|
defer timeout.Stop()
|
2017-09-30 01:51:15 -04:00
|
|
|
|
2017-09-24 17:13:50 -04:00
|
|
|
for {
|
2020-01-20 17:27:01 +01:00
|
|
|
timeout.Reset(500 * time.Second)
|
2017-09-24 17:13:50 -04:00
|
|
|
select {
|
2017-09-24 21:28:21 -04:00
|
|
|
case <-quit:
|
2017-09-24 17:13:50 -04:00
|
|
|
return
|
2020-01-20 17:27:01 +01:00
|
|
|
case <-timeout.C:
|
|
|
|
continue
|
|
|
|
case v := <-msgs:
|
2020-01-20 17:47:53 +01:00
|
|
|
hbSocket.RunWithSocket(func(echo zmq4.Socket) error {
|
2020-01-20 17:27:01 +01:00
|
|
|
if v.Err != nil {
|
|
|
|
log.Fatalf("Error reading heartbeat ping bytes: %v\n", v.Err)
|
|
|
|
return v.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the received byte string back to let the front-end know that the kernel is alive.
|
|
|
|
if err := echo.Send(v.Msg); err != nil {
|
|
|
|
log.Printf("Error sending heartbeat pong bytes: %b\n", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2017-09-24 17:13:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-10-31 21:37:12 -04:00
|
|
|
return quit
|
2017-09-24 21:28:21 -04:00
|
|
|
}
|
2020-01-09 20:03:21 +01:00
|
|
|
|
|
|
|
// find and execute special commands in code, remove them from returned string
|
|
|
|
func evalSpecialCommands(ir *interp.Interp, code string) string {
|
|
|
|
lines := strings.Split(code, "\n")
|
|
|
|
for i, line := range lines {
|
|
|
|
line = strings.TrimSpace(line)
|
|
|
|
if len(line) != 0 && line[0] == '%' {
|
|
|
|
evalSpecialCommand(ir, line)
|
|
|
|
lines[i] = ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
// execute special command
|
|
|
|
func evalSpecialCommand(ir *interp.Interp, line string) {
|
|
|
|
const help string = "available special commands:\n %go111module {on|off}\n %help"
|
|
|
|
|
|
|
|
args := strings.SplitN(line, " ", 2)
|
|
|
|
cmd := args[0]
|
|
|
|
arg := ""
|
|
|
|
if len(args) > 1 {
|
|
|
|
arg = args[1]
|
|
|
|
}
|
|
|
|
switch cmd {
|
|
|
|
|
|
|
|
case "%go111module":
|
|
|
|
if arg == "on" {
|
|
|
|
ir.Comp.CompGlobals.Options |= base.OptModuleImport
|
|
|
|
} else if arg == "off" {
|
|
|
|
ir.Comp.CompGlobals.Options &^= base.OptModuleImport
|
|
|
|
} else {
|
|
|
|
panic(fmt.Errorf("special command %s: expecting a single argument 'on' or 'off', found: %q", cmd, arg))
|
|
|
|
}
|
|
|
|
case "%help":
|
|
|
|
panic(help)
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unknown special command: %q\n%s", line, help))
|
|
|
|
}
|
|
|
|
}
|