"io"
"net"
"net/http"
+ "net/http/httptest"
_ "net/http/pprof"
"net/url"
"os"
"strings"
+ "time"
"git.arvados.org/arvados.git/lib/cmd"
"git.arvados.org/arvados.git/lib/config"
ctx context.Context // enables tests to shutdown service; no public API yet
}
+var requestQueueDumpCheckInterval = time.Minute
+
// Command returns a cmd.Handler that loads site config, calls
// newHandler with the current cluster and node configs, and brings up
// an http server with the returned handler.
return 1
}
+ maxReqs := cluster.API.MaxConcurrentRequests
+ if maxRails := cluster.API.MaxConcurrentRailsRequests; maxRails > 0 &&
+ (maxRails < maxReqs || maxReqs == 0) &&
+ strings.HasSuffix(prog, "controller") {
+ // Ideally, we would accept up to
+ // MaxConcurrentRequests, and apply the
+ // MaxConcurrentRailsRequests limit only for requests
+ // that require calling upstream to RailsAPI. But for
+ // now we make the simplifying assumption that every
+ // controller request causes an upstream RailsAPI
+ // request.
+ maxReqs = maxRails
+ }
instrumented := httpserver.Instrument(reg, log,
httpserver.HandlerWithDeadline(cluster.API.RequestTimeout.Duration(),
httpserver.AddRequestIDs(
httpserver.Inspect(reg, cluster.ManagementToken,
httpserver.LogRequests(
interceptHealthReqs(cluster.ManagementToken, handler.CheckHealth,
- httpserver.NewRequestLimiter(cluster.API.MaxConcurrentRequests, handler, reg)))))))
+ &httpserver.RequestLimiter{
+ Handler: handler,
+ MaxConcurrent: maxReqs,
+ MaxQueue: cluster.API.MaxQueuedRequests,
+ MaxQueueTimeForMinPriority: cluster.API.MaxQueueTimeForLockRequests.Duration(),
+ Priority: c.requestPriority,
+ Registry: reg}))))))
srv := &httpserver.Server{
Server: http.Server{
Handler: ifCollectionInHost(instrumented, instrumented.ServeAPI(cluster.ManagementToken, instrumented)),
<-handler.Done()
srv.Close()
}()
+ go c.requestQueueDumpCheck(cluster, maxReqs, prog, reg, &srv.Server, logger)
err = srv.Wait()
if err != nil {
return 1
return 0
}
+// If SystemLogs.RequestQueueDumpDirectory is set, monitor the
+// server's incoming HTTP request queue size. When it exceeds 90% of
+// API.MaxConcurrentRequests, write the /_inspect/requests data to a
+// JSON file in the specified directory.
+func (c *command) requestQueueDumpCheck(cluster *arvados.Cluster, maxReqs int, prog string, reg *prometheus.Registry, srv *http.Server, logger logrus.FieldLogger) {
+ outdir := cluster.SystemLogs.RequestQueueDumpDirectory
+ if outdir == "" || cluster.ManagementToken == "" || maxReqs < 1 {
+ return
+ }
+ logger = logger.WithField("worker", "RequestQueueDump")
+ outfile := outdir + "/" + prog + "-requests.json"
+ for range time.NewTicker(requestQueueDumpCheckInterval).C {
+ mfs, err := reg.Gather()
+ if err != nil {
+ logger.WithError(err).Warn("error getting metrics")
+ continue
+ }
+ dump := false
+ for _, mf := range mfs {
+ if mf.Name != nil && *mf.Name == "arvados_concurrent_requests" && len(mf.Metric) == 1 {
+ n := int(mf.Metric[0].GetGauge().GetValue())
+ if n > 0 && n >= maxReqs*9/10 {
+ dump = true
+ break
+ }
+ }
+ }
+ if dump {
+ req, err := http.NewRequest("GET", "/_inspect/requests", nil)
+ if err != nil {
+ logger.WithError(err).Warn("error in http.NewRequest")
+ continue
+ }
+ req.Header.Set("Authorization", "Bearer "+cluster.ManagementToken)
+ resp := httptest.NewRecorder()
+ srv.Handler.ServeHTTP(resp, req)
+ if code := resp.Result().StatusCode; code != http.StatusOK {
+ logger.WithField("StatusCode", code).Warn("error getting /_inspect/requests")
+ continue
+ }
+ err = os.WriteFile(outfile, resp.Body.Bytes(), 0777)
+ if err != nil {
+ logger.WithError(err).Warn("error writing file")
+ continue
+ }
+ }
+ }
+}
+
+func (c *command) requestPriority(req *http.Request, queued time.Time) int64 {
+ switch {
+ case req.Method == http.MethodPost && strings.HasPrefix(req.URL.Path, "/arvados/v1/containers/") && strings.HasSuffix(req.URL.Path, "/lock"):
+ // Return 503 immediately instead of queueing. We want
+ // to send feedback to dispatchcloud ASAP to stop
+ // bringing up new containers.
+ return httpserver.MinPriority
+ case req.Method == http.MethodPost && strings.HasPrefix(req.URL.Path, "/arvados/v1/logs"):
+ // "Create log entry" is the most harmless kind of
+ // request to drop. Negative priority is called "low"
+ // in aggregate metrics.
+ return -1
+ case req.Header.Get("Origin") != "":
+ // Handle interactive requests first. Positive
+ // priority is called "high" in aggregate metrics.
+ return 1
+ default:
+ // Zero priority is called "normal" in aggregate
+ // metrics.
+ return 0
+ }
+}
+
// If an incoming request's target vhost has an embedded collection
// UUID or PDH, handle it with hTrue, otherwise handle it with
// hFalse.