// createACSClient creates the ACS Client using the specified URL func (acsResources *acsSessionResources) createACSClient(url string) wsclient.ClientServer { args := acsResources.startSessionArguments cfg := args.Config return acsclient.New(url, cfg.AWSRegion, args.CredentialProvider, args.AcceptInvalidCert) }
// StartSession creates a session with ACS and handles requests using the passed // in arguments. func StartSession(ctx context.Context, args StartSessionArguments) error { ecsclient := args.ECSClient cfg := args.Config backoff := utils.NewSimpleBackoff(connectionBackoffMin, connectionBackoffMax, connectionBackoffJitter, connectionBackoffMultiplier) payloadBuffer := make(chan *ecsacs.PayloadMessage, payloadMessageBufferSize) ackBuffer := make(chan string, payloadMessageBufferSize) go func() { // Handle any payloads async. For correctness, they must be handled in order, hence the buffered channel which is added to synchronously. for { select { case payload := <-payloadBuffer: handlePayloadMessage(ackBuffer, cfg.Cluster, args.ContainerInstanceArn, payload, args.TaskEngine, ecsclient, args.StateManager) case <-ctx.Done(): return } } }() for { acsError := func() error { acsEndpoint, err := ecsclient.DiscoverPollEndpoint(args.ContainerInstanceArn) if err != nil { log.Error("Unable to discover poll endpoint", "err", err) return err } log.Debug("Connecting to ACS endpoint " + acsEndpoint) url := AcsWsUrl(acsEndpoint, cfg.Cluster, args.ContainerInstanceArn, args.TaskEngine) clearStrChannel(ackBuffer) client := acsclient.New(url, cfg.AWSRegion, args.CredentialProvider, args.AcceptInvalidCert) defer client.Close() // Clear the ackbuffer whenever we get a new client because acks of // messageids don't have any value across sessions defer clearStrChannel(ackBuffer) timer := ttime.AfterFunc(utils.AddJitter(heartbeatTimeout, heartbeatJitter), func() { log.Warn("ACS Connection hasn't had any activity for too long; closing connection") closeErr := client.Close() if closeErr != nil { log.Warn("Error disconnecting: " + closeErr.Error()) } }) defer timer.Stop() // Any message from the server resets the disconnect timeout client.SetAnyRequestHandler(anyMessageHandler(timer)) client.AddRequestHandler(payloadMessageHandler(payloadBuffer)) // Ignore heartbeat messages; anyMessageHandler gets 'em client.AddRequestHandler(func(*ecsacs.HeartbeatMessage) {}) updater.AddAgentUpdateHandlers(client, cfg, args.StateManager, args.TaskEngine) err = client.Connect() if err != nil { log.Error("Error connecting to ACS: " + err.Error()) return err } ttime.AfterFunc(utils.AddJitter(heartbeatTimeout, heartbeatJitter), func() { // If we do not have an error connecting and remain connected for at // least 5 or so minutes, reset the backoff. This prevents disconnect // errors that only happen infrequently from damaging the // reconnectability as significantly. backoff.Reset() }) serveErr := make(chan error, 1) go func() { serveErr <- client.Serve() }() for { select { case mid := <-ackBuffer: ackMessageId(client, cfg.Cluster, args.ContainerInstanceArn, mid) case <-ctx.Done(): return ctx.Err() case err := <-serveErr: return err } } }() select { case <-ctx.Done(): return ctx.Err() default: } if acsError == nil || acsError == io.EOF { backoff.Reset() } else { log.Info("Error from acs; backing off", "err", acsError) ttime.Sleep(backoff.Duration()) } } }