func newConnectionHandler(ctx context.Context, newDisp dispatcherInit) (*ConnectionHandler, error) { connID := getID(ctx) ctx = apexctx.WithLogger(ctx, apexctx.GetLogger(ctx).WithField("conn.id", connID)) return &ConnectionHandler{ ctx: ctx, sessions: newSessions(), highestChannel: 0, newDispatcher: newDisp, connID: connID, }, nil }
// HandleConn decodes commands from Cocaine runtime and calls dispatchers func (h *ConnectionHandler) HandleConn(conn io.ReadWriteCloser) { defer func() { conn.Close() apexctx.GetLogger(h.ctx).Errorf("Connection has been closed") }() ctx, cancel := context.WithCancel(h.ctx) defer cancel() logger := apexctx.GetLogger(h.ctx) r := msgp.NewReader(conn) LOOP: for { hasHeaders, channel, c, err := h.next(r) if err != nil { if err == io.EOF { return } apexctx.GetLogger(h.ctx).WithError(err).Errorf("next(): unable to read message") return } logger.Infof("channel %d, number %d", channel, c) dispatcher, ok := h.sessions.Get(channel) if !ok { if channel <= h.highestChannel { // dispatcher was detached from ResponseStream.OnClose // This message must be `close` message. // `channel`, `number` are parsed, skip `args` and probably `headers` logger.Infof("dispatcher for channel %d was detached", channel) r.Skip() if hasHeaders { r.Skip() } continue LOOP } h.highestChannel = channel ctx = apexctx.WithLogger(ctx, logger.WithField("channel", fmt.Sprintf("%s.%d", h.connID, channel))) rs := newResponseStream(ctx, conn, channel) rs.OnClose(func(ctx context.Context) { h.sessions.Detach(channel) }) dispatcher = h.newDispatcher(ctx, rs) } dispatcher, err = dispatcher.Handle(c, r) // NOTE: remove it when the headers are being handling properly if hasHeaders { r.Skip() } if err != nil { if err == ErrInvalidArgsNum { logger.WithError(err).Errorf("channel %d, number %d", channel, c) return } logger.WithError(err).Errorf("Handle returned an error") h.sessions.Detach(channel) continue LOOP } if dispatcher == nil { h.sessions.Detach(channel) continue LOOP } h.sessions.Attach(channel, dispatcher) } }
func main() { if showVersion { printVersion() return } data, err := ioutil.ReadFile(configpath) if err != nil { fmt.Fprintf(os.Stderr, "unable to read config: %v\n", err) os.Exit(1) } config, err := config.Parse(data) if err != nil { fmt.Fprintf(os.Stderr, "config is invalid: %v\n", err) os.Exit(1) } if config.Version != requiredConfigVersion { fmt.Fprintf(os.Stderr, "invalid config version (%d). %d is required\n", config.Version, requiredConfigVersion) os.Exit(1) } output, err := logutils.NewLogFileOutput(config.Logger.Output) if err != nil { fmt.Fprintf(os.Stderr, "unable to open logfile output: %v\n", err) os.Exit(1) } defer output.Close() logger := &log.Logger{ Level: log.Level(config.Logger.Level), Handler: logutils.NewLogHandler(output), } ctx := apexctx.WithLogger(apexctx.Background(), log.NewEntry(logger)) ctx, cancelFunc := context.WithCancel(ctx) defer cancelFunc() switch name := config.Metrics.Type; name { case "graphite": var cfg exportmetrics.GraphiteConfig if err = json.Unmarshal(config.Metrics.Args, &cfg); err != nil { logger.WithError(err).WithField("name", name).Fatal("unable to decode graphite exporter config") } sender, err := exportmetrics.NewGraphiteExporter(&cfg) if err != nil { logger.WithError(err).WithField("name", name).Fatal("unable to create GraphiteExporter") } minimalPeriod := 5 * time.Second period := time.Duration(config.Metrics.Period) if period < minimalPeriod { logger.Warnf("metrics: specified period is too low. Set %s", minimalPeriod) period = minimalPeriod } go func(ctx context.Context, p time.Duration) { for { select { case <-time.After(p): if err := sender.Send(ctx, metrics.DefaultRegistry); err != nil { logger.WithError(err).WithField("name", name).Error("unable to send metrics") } case <-ctx.Done(): return } } }(ctx, period) case "": logger.Warn("metrics: exporter is not specified") default: logger.WithError(err).WithField("exporter", name).Fatal("unknown exporter") } go func() { collect(ctx) for range time.Tick(30 * time.Second) { collect(ctx) } }() checkLimits(ctx) boxTypes := map[string]struct{}{} boxes := isolate.Boxes{} for name, cfg := range config.Isolate { if _, ok := boxTypes[cfg.Type]; ok { logger.WithField("box", name).WithField("type", cfg.Type).Fatal("dublicated box type") } boxCtx := apexctx.WithLogger(ctx, logger.WithField("box", name)) box, err := isolate.ConstructBox(boxCtx, cfg.Type, cfg.Args) if err != nil { logger.WithError(err).WithField("box", name).WithField("type", cfg.Type).Fatal("unable to create box") } boxes[name] = box boxTypes[cfg.Type] = struct{}{} } ctx = context.WithValue(ctx, isolate.BoxesTag, boxes) if config.DebugServer != "" { logger.WithField("endpoint", config.DebugServer).Info("start debug HTTP-server") go func() { logger.WithError(http.ListenAndServe(config.DebugServer, nil)).Error("debug server is listening") }() } var wg sync.WaitGroup for _, endpoint := range config.Endpoints { logger.WithField("endpoint", endpoint).Info("start TCP server") ln, err := net.Listen("tcp", endpoint) if err != nil { logger.WithError(err).WithField("endpoint", endpoint).Fatal("unable to listen to") } defer ln.Close() wg.Add(1) func(ln net.Listener) { defer wg.Done() lnLogger := logger.WithField("listener", ln.Addr()) for { conn, err := ln.Accept() if err != nil { lnLogger.WithError(err).Error("Accept") continue } // TODO: more optimal way connID := fmt.Sprintf("%.4x", md5.Sum([]byte(fmt.Sprintf("%s.%d", conn.RemoteAddr().String(), time.Now().Unix())))) lnLogger.WithFields(log.Fields{"remote.addr": conn.RemoteAddr(), "conn.id": connID}).Info("accepted new connection") connHandler, err := isolate.NewConnectionHandler(context.WithValue(ctx, "conn.id", connID)) if err != nil { lnLogger.WithError(err).Fatal("unable to create connection handler") } go func() { conns.Inc(1) defer conns.Dec(1) connHandler.HandleConn(conn) }() } }(ln) } wg.Wait() }