13697: Cancel request context after API.RequestTimeout.
[arvados.git] / lib / service / cmd.go
index 0391c5a043dd75e6d3cdaa38f6ba31c9c4dec3ca..e67c24f65f39cea4929c95fe30abbdc5ab98a901 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: Apache-2.0
 
-// package service provides a cmd.Handler that brings up a system service.
+// Package service provides a cmd.Handler that brings up a system service.
 package service
 
 import (
@@ -12,14 +12,16 @@ import (
        "io"
        "net"
        "net/http"
+       _ "net/http/pprof"
+       "net/url"
        "os"
        "strings"
 
-       "git.curoverse.com/arvados.git/lib/cmd"
-       "git.curoverse.com/arvados.git/lib/config"
-       "git.curoverse.com/arvados.git/sdk/go/arvados"
-       "git.curoverse.com/arvados.git/sdk/go/ctxlog"
-       "git.curoverse.com/arvados.git/sdk/go/httpserver"
+       "git.arvados.org/arvados.git/lib/cmd"
+       "git.arvados.org/arvados.git/lib/config"
+       "git.arvados.org/arvados.git/sdk/go/arvados"
+       "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "git.arvados.org/arvados.git/sdk/go/httpserver"
        "github.com/coreos/go-systemd/daemon"
        "github.com/prometheus/client_golang/prometheus"
        "github.com/sirupsen/logrus"
@@ -28,6 +30,7 @@ import (
 type Handler interface {
        http.Handler
        CheckHealth() error
+       Done() <-chan struct{}
 }
 
 type NewHandlerFunc func(_ context.Context, _ *arvados.Cluster, token string, registry *prometheus.Registry) Handler
@@ -58,7 +61,7 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        var err error
        defer func() {
                if err != nil {
-                       log.WithError(err).Info("exiting")
+                       log.WithError(err).Error("exiting")
                }
        }()
 
@@ -68,6 +71,7 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        loader := config.NewLoader(stdin, log)
        loader.SetupFlags(flags)
        versionFlag := flags.Bool("version", false, "Write version information to stdout and exit 0")
+       pprofAddr := flags.String("pprof", "", "Serve Go profile data at `[addr]:port`")
        err = flags.Parse(args)
        if err == flag.ErrHelp {
                err = nil
@@ -78,6 +82,12 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
                return cmd.Version.RunCommand(prog, args, stdin, stdout, stderr)
        }
 
+       if *pprofAddr != "" {
+               go func() {
+                       log.Println(http.ListenAndServe(*pprofAddr, nil))
+               }()
+       }
+
        if strings.HasSuffix(prog, "controller") {
                // Some config-loader checks try to make API calls via
                // controller. Those can't be expected to work if this
@@ -116,13 +126,14 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        }
 
        instrumented := httpserver.Instrument(reg, log,
-               httpserver.HandlerWithContext(ctx,
+               httpserver.HandlerWithDeadline(cluster.API.RequestTimeout.Duration(),
                        httpserver.AddRequestIDs(
                                httpserver.LogRequests(
                                        httpserver.NewRequestLimiter(cluster.API.MaxConcurrentRequests, handler, reg)))))
        srv := &httpserver.Server{
                Server: http.Server{
-                       Handler: instrumented.ServeAPI(cluster.ManagementToken, instrumented),
+                       Handler:     instrumented.ServeAPI(cluster.ManagementToken, instrumented),
+                       BaseContext: func(net.Listener) context.Context { return ctx },
                },
                Addr: listenURL.Host,
        }
@@ -147,9 +158,15 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
                logger.WithError(err).Errorf("error notifying init daemon")
        }
        go func() {
+               // Shut down server if caller cancels context
                <-ctx.Done()
                srv.Close()
        }()
+       go func() {
+               // Shut down server if handler dies
+               <-handler.Done()
+               srv.Close()
+       }()
        err = srv.Wait()
        if err != nil {
                return 1
@@ -157,30 +174,42 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        return 0
 }
 
-const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
-
 func getListenAddr(svcs arvados.Services, prog arvados.ServiceName, log logrus.FieldLogger) (arvados.URL, error) {
        svc, ok := svcs.Map()[prog]
        if !ok {
                return arvados.URL{}, fmt.Errorf("unknown service name %q", prog)
        }
-       for url := range svc.InternalURLs {
-               if strings.HasPrefix(url.Host, "localhost:") {
-                       return url, nil
+
+       if want := os.Getenv("ARVADOS_SERVICE_INTERNAL_URL"); want == "" {
+       } else if url, err := url.Parse(want); err != nil {
+               return arvados.URL{}, fmt.Errorf("$ARVADOS_SERVICE_INTERNAL_URL (%q): %s", want, err)
+       } else {
+               if url.Path == "" {
+                       url.Path = "/"
                }
+               return arvados.URL(*url), nil
+       }
+
+       errors := []string{}
+       for url := range svc.InternalURLs {
                listener, err := net.Listen("tcp", url.Host)
                if err == nil {
                        listener.Close()
                        return url, nil
                } else if strings.Contains(err.Error(), "cannot assign requested address") {
+                       // If 'Host' specifies a different server than
+                       // the current one, it'll resolve the hostname
+                       // to IP address, and then fail because it
+                       // can't bind an IP address it doesn't own.
                        continue
-               } else if strings.Contains(err.Error(), "address already in use") {
-                       return url, err
                } else {
-                       log.Warn(err)
+                       errors = append(errors, fmt.Sprintf("tried %v, got %v", url, err))
                }
        }
-       return arvados.URL{}, fmt.Errorf("configuration does not enable the %s service on this host", prog)
+       if len(errors) > 0 {
+               return arvados.URL{}, fmt.Errorf("could not enable the %q service on this host: %s", prog, strings.Join(errors, "; "))
+       }
+       return arvados.URL{}, fmt.Errorf("configuration does not enable the %q service on this host", prog)
 }
 
 type contextKeyURL struct{}