func main() { go func() { // goto: http://localhost:6060/debug/pprof/ for goroutine info! fmt.Println(http.ListenAndServe("localhost:6060", nil)) }() bcfg := babel.BotConfig{ Nick: "babelbot", RoomName: "test", Cmds: []plugin.CmdPluginDialer{ plugin.CmdPluginDialer{ Cmd: "python", Args: []string{"log_everything.py"}, }, }, } ws := &connection.WSConnectionDialer{ UrlFormat: connection.EUPHORIA_URL_FORMAT, } ctx := scope.New() proto.SetLogger(ctx, logrus.StandardLogger()) err := bcfg.Start(ctx, ws) if err != nil { proto.GetLogger(ctx).Errorf("bcfg.Start failure in main(): %s", err) } }
// Start is a non-blocking operation that starts listening for packets from outgoing // and streaming packets to incoming. func (c *WSConnection) start() { localIncoming := make(chan *hproto.Packet, 1) go c.receivePacket(localIncoming) go func() { for { select { // Send packet case msg := <-c.outgoing: if err := c.conn.WriteJSON(msg); err != nil { c.Kill(err) } // Dispatch packet case msg := <-localIncoming: c.incoming <- msg go c.receivePacket(localIncoming) // We're done case <-c.ctx.Done(): return // Placeholder for debugging or monitoring code case <-time.After(time.Second * 30): proto.GetLogger(c.ctx).Debugf("WSConnection to %s still looping.", c.roomName) } } }() }
func (d *CmdPluginDialer) Dial(ctx scope.Context) (proto.Plugin, error) { cmdPlugin := &CmdPlugin{ ctx: ctx.Fork(), cmd: d.Cmd, args: d.Args, } err := cmdPlugin.connect() if err != nil { proto.GetLogger(cmdPlugin.ctx).Errorf("Failed to connect CmdPlugin: %s", err) return nil, err } return cmdPlugin, nil }
func (p *RetryPlugin) ReceiveOut() (*proto.Packet, error) { p.lock.Lock() plugin := p.plugin p.lock.Unlock() packet, err := plugin.ReceiveOut() if err != nil { proto.GetLogger(p.ctx).Warningf("Error receiving out from retry-wrapped plugin: %s", err) startErr := p.start(plugin) if startErr != nil { return nil, startErr } return p.ReceiveOut() } return packet, nil }
func (p *RetryPlugin) Send(packet *proto.Packet) error { p.lock.Lock() plugin := p.plugin p.lock.Unlock() err := plugin.Send(packet) if err != nil { proto.GetLogger(p.ctx).Warningf("Error sending to retry-wrapped plugin: %s", err) startErr := p.start(plugin) if startErr != nil { return startErr } return plugin.Send(packet) } return nil }
func (c *RetryConnection) Receive() (*hproto.Packet, error) { c.lock.Lock() conn := c.conn c.lock.Unlock() packet, err := conn.Receive() if err != nil { proto.GetLogger(c.ctx).Infof("Error receiving from retry-wrapped connection: %s", err) startErr := c.start(conn) if startErr != nil { return nil, startErr } return c.Receive() } return packet, nil }
func (c *RetryConnection) Send(packet *hproto.Packet) error { c.lock.Lock() conn := c.conn c.lock.Unlock() err := conn.Send(packet) if err != nil { proto.GetLogger(c.ctx).Infof("Error sending to retry-wrapped connection: %s", err) startErr := c.start(conn) if startErr != nil { return startErr } return c.Send(packet) } return nil }
func (pg *CmdPlugin) listen() { proto.GetLogger(pg.ctx).Infof("CmdPlugin %s started", pg.Name()) stdoutChan := make(chan *proto.Packet) // TODO: break this out into a separate function go func() { p, err := pg.readOut() if err != nil { pg.Kill(err) return } stdoutChan <- p }() stderrChan := make(chan error) // TODO: break this out into a separate function go func() { stderr, err := pg.readErr() if err != nil { pg.Kill(fmt.Errorf("Plugin error: %s", err)) return } stderrChan <- stderr }() for { select { case p := <-stdoutChan: pg.stdoutChan <- p go func() { p, err := pg.readOut() if err != nil { pg.Kill(err) return } stdoutChan <- p }() case p := <-pg.stdinChan: if err := pg.writeIn(p); err != nil { pg.Kill(err) return } case <-pg.ctx.Done(): return } } }
func (d *WSConnectionDialer) Dial(ctx scope.Context, roomName string) (proto.Connection, error) { ws := WSConnection{ roomName: roomName, ctx: ctx.Fork(), incoming: make(chan *hproto.Packet, 8), outgoing: make(chan *hproto.Packet, 8)} dialer := websocket.Dialer{ HandshakeTimeout: 5 * time.Second, } url := fmt.Sprintf(d.UrlFormat, ws.roomName) proto.GetLogger(ws.ctx).Infof("Connecting to: %s", url) conn, _, err := dialer.Dial(url, nil) if err != nil { return nil, err } ws.conn = conn ws.start() return &ws, nil }
func (p *RetryPlugin) start(errorPlugin proto.Plugin) error { p.lock.Lock() defer p.lock.Unlock() if errorPlugin == p.plugin { for p.retries < 3 { p.retries++ plugin, err := p.dialer.Dial(p.ctx) if err != nil { proto.GetLogger(p.ctx).Infof("Error dialing retry-wrapped plugin: %s", err) continue } p.plugin = plugin return nil } err := fmt.Errorf("Too many retries") p.Kill(err) return p.ctx.Err() } return nil }
func (c *RetryConnection) start(errorConn proto.Connection) error { c.lock.Lock() defer c.lock.Unlock() if errorConn == c.conn { for c.retries < 3 { // This retrying stuff needs a serious upgrade c.retries++ conn, err := c.dialer.Dial(c.ctx, c.roomName) if err != nil { // Try again after logging the failure proto.GetLogger(c.ctx).Infof("Error connecting with retry-wrapped connection: %s", err) continue } c.conn = conn return nil } err := fmt.Errorf("Too many retries") c.Kill(err) return c.ctx.Err() } return nil }
func (b *Bot) Kill(err error) { if b.ctx.Alive() { proto.GetLogger(b.ctx).Errorf("Bot killed: %s", err) b.ctx.Terminate(err) } }