// handle is the per-connection i/o goroutine. // buffers data into readBuffer and flushes data from writeBuffer. // if the disconnect channel is signalled, breaks the main loop and closes the connection func (s *Server) handle(netConn net.Conn) { conn := NewConnection(netConn, logger) client, err := s.handshake(conn) if err == nil && client != nil { s.registerClient(client) defer s.unregisterClient(client) defer safe.Recover(conn.Log()) conn.Log().Info("accepted connection") go encodeFromWriteQueue(client) go decodeToReadQueue(client) // Block this thread until disconnect <-conn.DisconnectChan // ensure any pending data is flushed before disconnecting conn.flushWriteBuffer() } close(conn.Read) close(conn.Write) conn.conn.Close() conn.Log().Info("connection closed") }
// decodeToReadQueue is the goroutine handling the read buffer // reads from the buffer, decodes Codables using conn.decode, which can choose // to either handle the data or place a Codable into the read queue func decodeToReadQueue(client Client) { conn := client.Conn() defer safe.Recover(conn.Log()) for { err := conn.fillReadBuffer() if err != nil { conn.Log().Debug("read error: %v", err) conn.Disconnect() break } // at this point, the only error should be because we didn't have enough data // todo: formalize this and check for the right error canTrim := false toRead := conn.ReadBuffer.Len() for toRead > 0 && err == nil { err = conn.ReadBuffer.Try(func(b *encoding.Buffer) error { return client.Decode() }) if err == nil { canTrim = true } } if err != nil && err != io.EOF { conn.Log().Error("decode returned non EOF error: %v", err) } if canTrim { // We handled some data, discard it conn.ReadBuffer.Trim() } } }
// encodeFromWriteQueue is the goroutine handling the write buffer // picks from conn.write, encodes the Codables, and flushes the write buffer func encodeFromWriteQueue(client Client) { conn := client.Conn() defer safe.Recover(conn.Log()) L: for { select { case codable, ok := <-conn.Write: if ok { select { case <-conn.DisconnectChan: ok = false default: } } if !ok { break L } err := client.Encode(codable) if err == nil { err = conn.flushWriteBuffer() } if err != nil { conn.Log().Debug("write error: %v", err) conn.Disconnect() break L } } } }
func (l *PyListener) Notify(e *Event, args ...interface{}) { lock := py.NewLock() defer lock.Unlock() argsIn := []interface{}{e} argsIn = append(argsIn, args...) log := e.log.Child("py_listener", log.MapContext{"id": l.id}) defer safe.Recover(log) argsOut := []py.Object{} for _, a := range argsIn { converted, err := pybind.TypeConvOut(a, "") if err != nil { panic(err) } converted.Incref() argsOut = append(argsOut, converted) } _, err := l.fn.Base().CallFunctionObjArgs(argsOut...) if err != nil { // Panicing with the whole py error object causes a double panic. // Suspect this is because python has cleaned up by the time the runtime evals the error panic(err.Error()) } }
func (l *Listener) Notify(e *Event, args ...interface{}) { log := e.log.Child("listener", log.MapContext{"id": l.id}) defer safe.Recover(log) select { case <-l.owner.Expired(): // If the owner expired, unregister this listener e.Unregister(l) default: l.fn(e, args...) } }
func (task *Task) Tick() bool { defer safe.Recover(task.logger) task.counter = task.counter - 1 if task.counter == 0 { reschedule := task.Callback(task) if reschedule { task.counter = task.Interval } } return task.counter == 0 }
func (e *Engine) run() error { // Start the engine ticking... preTask := task.NewTask(func(*task.Task) bool { engine_event.PreTick.NotifyObservers() return true }, task.PreTick, 1, nil) duringTask := task.NewTask(func(*task.Task) bool { engine_event.Tick.NotifyObservers() return true }, task.Tick, 1, nil) postTask := task.NewTask(func(*task.Task) bool { engine_event.PostTick.NotifyObservers() return true }, task.PostTick, 1, nil) task.Scheduler.Submit(preTask) task.Scheduler.Submit(duringTask) task.Scheduler.Submit(postTask) // Main engine loop c := time.Tick(EngineTick) for _ = range c { if !e.t.Alive() { break } func() { defer safe.Recover(logger) task.Scheduler.Tick(task.PreTick) task.Scheduler.Tick(task.Tick) task.Scheduler.Tick(task.PostTick) }() } return nil }