github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/jupyter/gojupyterscaffold/gojupyterscaffold.go (about) 1 // Package gojupyterscaffold provides a scaffold of Jupyter kernel implemented by Go. 2 // 3 // References: 4 // https://github.com/ipython/ipykernel/blob/master/ipykernel/kernelbase.py 5 // https://github.com/jupyter/jupyter_client/blob/master/jupyter_client/session.py 6 // 7 // Misc: 8 // ZMQ pubsub with inproc is broken (https://github.com/JustinTulloss/zeromq.node/issues/22) though it's not used in this code now. 9 package gojupyterscaffold 10 11 import ( 12 "context" 13 "encoding/json" 14 "fmt" 15 "io/ioutil" 16 "os" 17 "os/signal" 18 "syscall" 19 20 zmq "github.com/pebbe/zmq4" 21 ) 22 23 // ConnectionInfo stores the contents of the kernel connection file created by Jupyter. 24 type connectionInfo struct { 25 StdinPort int `json:"stdin_port"` 26 IP string `json:"ip"` 27 ControlPort int `json:"control_port"` 28 HBPort int `json:"hb_port"` 29 SignatureScheme string `json:"signature_scheme"` 30 Key string `json:"key"` 31 KernelName string `json:"kernel_name"` 32 ShellPort int `json:"shell_port"` 33 Transport string `json:"transport"` 34 IOPubPort int `json:"iopub_port"` 35 } 36 37 func readConnectionInfo(connectionFile string) (*connectionInfo, error) { 38 b, err := ioutil.ReadFile(connectionFile) 39 if err != nil { 40 return nil, fmt.Errorf("Failed to read %s: %v", connectionFile, err) 41 } 42 logger.Infof("Connection info JSON: %v", string(b)) 43 var cinfo connectionInfo 44 if err = json.Unmarshal(b, &cinfo); err != nil { 45 return nil, fmt.Errorf("Failed to parse %s: %v", connectionFile, err) 46 } 47 logger.Infof("Connection info: %+v", cinfo) 48 return &cinfo, nil 49 } 50 51 func (ci *connectionInfo) getAddr(port int) string { 52 return fmt.Sprintf("%s://%s:%d", ci.Transport, ci.IP, port) 53 } 54 55 // A Server is a jupyter kernel server that that handles user commands forwarded from 56 // Jupyter frontend servers. 57 type Server struct { 58 handlers RequestHandlers 59 60 // ctx of this server and a func to cancel it. 61 ctx context.Context 62 cancelCtx func() 63 64 // ZMQ sockets 65 shell *shellSocket 66 control *shellSocket 67 iopub *iopubSocket 68 stdin *zmq.Socket 69 hb *zmq.Socket 70 71 // Attribute 72 connInfo *connectionInfo 73 74 execQueue *executeQueue 75 } 76 77 // NewServer returns a new jupyter kernel server. 78 func NewServer(bgCtx context.Context, connectionFile string, handlers RequestHandlers) (server *Server, err error) { 79 serverCtx, cancelCtx := context.WithCancel(bgCtx) 80 defer func() { 81 // Avoid ctx leak 82 // https://golang.org/pkg/context/ 83 if server == nil { 84 cancelCtx() 85 } 86 }() 87 cinfo, err := readConnectionInfo(connectionFile) 88 if err != nil { 89 return nil, err 90 } 91 ctx, err := zmq.NewContext() 92 if err != nil { 93 return nil, err 94 } 95 96 iopub, err := newIOPubSocket(serverCtx, ctx, cinfo) 97 if err != nil { 98 return nil, fmt.Errorf("Failed to create iopub socket: %v", err) 99 } 100 101 execQueue := newExecuteQueue(serverCtx, iopub, handlers) 102 shell, err := newShellSocket(serverCtx, ctx, "shell", cinfo, iopub, handlers, cancelCtx, execQueue) 103 if err != nil { 104 return nil, fmt.Errorf("Failed to create shell socket: %v", err) 105 } 106 control, err := newShellSocket(serverCtx, ctx, "control", cinfo, iopub, handlers, cancelCtx, execQueue) 107 if err != nil { 108 return nil, fmt.Errorf("Failed to create control socket: %v", err) 109 } 110 111 stdin, err := ctx.NewSocket(zmq.ROUTER) 112 if err != nil { 113 return nil, fmt.Errorf("Failed to open stdin socket: %v", err) 114 } 115 if err := stdin.Bind(cinfo.getAddr(cinfo.StdinPort)); err != nil { 116 return nil, fmt.Errorf("Failed to bind shell socket: %v", err) 117 } 118 // Ref: Python version of HeartBeat 119 // https://github.com/ipython/ipykernel/blob/master/ipykernel/heartbeat.py 120 hb, err := ctx.NewSocket(zmq.REP) 121 if err != nil { 122 return nil, fmt.Errorf("Failed to open heartbeat socket: %v", err) 123 } 124 if err := hb.Bind(cinfo.getAddr(cinfo.HBPort)); err != nil { 125 return nil, fmt.Errorf("Failed to bind heartbeat socket: %v", err) 126 } 127 return &Server{ 128 handlers: handlers, 129 ctx: serverCtx, 130 cancelCtx: cancelCtx, 131 shell: shell, 132 control: control, 133 stdin: stdin, 134 iopub: iopub, 135 hb: hb, 136 connInfo: cinfo, 137 execQueue: execQueue, 138 }, nil 139 } 140 141 // Context returns the context of the server 142 func (s *Server) Context() context.Context { 143 return s.ctx 144 } 145 146 func (s *Server) monitorSigint() { 147 ch := make(chan os.Signal, 1) 148 signal.Notify(ch, syscall.SIGINT) 149 go func() { 150 for range ch { 151 logger.Info("Received SIGINT. Cancelling an ongoing execute_request") 152 s.execQueue.cancelCurrent() 153 } 154 }() 155 } 156 157 func (s *Server) monitorTerminationSignals() { 158 ch := make(chan os.Signal, 1) 159 signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) 160 go func() { 161 for sig := range ch { 162 logger.Infof("Received a signal (%s), terminating the kernel.", sig) 163 s.cancelCtx() 164 } 165 }() 166 } 167 168 func isEINTR(err error) bool { 169 if err == nil { 170 return false 171 } 172 errno, ok := err.(syscall.Errno) 173 return ok && errno == syscall.EINTR 174 } 175 176 // Loop starts the server main loop 177 func (s *Server) Loop() { 178 go func() { 179 logger.Info("Forwarding heartbeat requests") 180 if err := zmq.Proxy(s.hb, s.hb, nil); err != nil { 181 logger.Fatalf("Failed to echo heartbeat request: %v", err) 182 } 183 logger.Info("Quitting goroutine for heartbeat requests") 184 }() 185 s.monitorSigint() 186 s.monitorTerminationSignals() 187 188 execDone := make(chan struct{}) 189 sockDone := make(chan struct{}) 190 go func() { 191 s.execQueue.loop() 192 close(execDone) 193 }() 194 go func() { 195 s.shell.loop() 196 sockDone <- struct{}{} 197 }() 198 go func() { 199 s.control.loop() 200 sockDone <- struct{}{} 201 }() 202 <-execDone 203 204 if err := s.shell.notifyLoopEnd(); err != nil { 205 logger.Errorf("Failed to notify the loop end to shell socket: %v", err) 206 } 207 if err := s.control.notifyLoopEnd(); err != nil { 208 logger.Errorf("Failed to notify the loop end to control socket: %v", err) 209 } 210 // Wait loop ends 211 <-sockDone 212 <-sockDone 213 214 if err := s.iopub.close(); err != nil { 215 logger.Errorf("Failed to close iopub socket: %v", err) 216 } 217 if err := s.shell.close(); err != nil { 218 logger.Errorf("Failed to close shell socket: %v", err) 219 } 220 if err := s.control.close(); err != nil { 221 logger.Errorf("Failed to close control socket: %v", err) 222 } 223 224 // TODO: Support stdin. 225 }