Kaynağa Gözat

api: use singleflight for /info endpoint

Prevent potential suggestion when many concurrent requests happen on
the /info endpoint. It's worth noting that with this change,
requests to the endpoint while another request is still in flight
will share the results, hence might be slightly incorrect (for example,
the output includes SystemTime, which may now be incorrect).

Assuming that under normal circumstances, requests will still
happen fast enough to not be shared, this may not be a problem,
but we could decide to update specific fields to not be shared.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Sebastiaan van Stijn 2 yıl önce
ebeveyn
işleme
a1c9a686b0

+ 7 - 0
api/server/router/system/system.go

@@ -2,7 +2,9 @@ package system // import "github.com/docker/docker/api/server/router/system"
 
 import (
 	"github.com/docker/docker/api/server/router"
+	"github.com/docker/docker/api/types"
 	buildkit "github.com/docker/docker/builder/builder-next"
+	"resenje.org/singleflight"
 )
 
 // systemRouter provides information about the Docker system overall.
@@ -13,6 +15,11 @@ type systemRouter struct {
 	routes   []router.Route
 	builder  *buildkit.Builder
 	features func() map[string]bool
+
+	// collectSystemInfo is a single-flight for the /info endpoint,
+	// unique per API version (as different API versions may return
+	// a different API response).
+	collectSystemInfo singleflight.Group[string, *types.Info]
 }
 
 // NewRouter initializes a new system router

+ 30 - 27
api/server/router/system/system_routes.go

@@ -57,38 +57,41 @@ func (s *systemRouter) swarmStatus() string {
 }
 
 func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	info := s.backend.SystemInfo()
-
-	if s.cluster != nil {
-		info.Swarm = s.cluster.Info()
-		info.Warnings = append(info.Warnings, info.Swarm.Warnings...)
-	}
-
 	version := httputils.VersionFromContext(ctx)
-	if versions.LessThan(version, "1.25") {
-		// TODO: handle this conversion in engine-api
-		kvSecOpts, err := types.DecodeSecurityOptions(info.SecurityOptions)
-		if err != nil {
-			info.Warnings = append(info.Warnings, err.Error())
+	info, _, _ := s.collectSystemInfo.Do(ctx, version, func(ctx context.Context) (*types.Info, error) {
+		info := s.backend.SystemInfo()
+
+		if s.cluster != nil {
+			info.Swarm = s.cluster.Info()
+			info.Warnings = append(info.Warnings, info.Swarm.Warnings...)
 		}
-		var nameOnly []string
-		for _, so := range kvSecOpts {
-			nameOnly = append(nameOnly, so.Name)
+
+		if versions.LessThan(version, "1.25") {
+			// TODO: handle this conversion in engine-api
+			kvSecOpts, err := types.DecodeSecurityOptions(info.SecurityOptions)
+			if err != nil {
+				info.Warnings = append(info.Warnings, err.Error())
+			}
+			var nameOnly []string
+			for _, so := range kvSecOpts {
+				nameOnly = append(nameOnly, so.Name)
+			}
+			info.SecurityOptions = nameOnly
+			info.ExecutionDriver = "<not supported>" //nolint:staticcheck // ignore SA1019 (ExecutionDriver is deprecated)
 		}
-		info.SecurityOptions = nameOnly
-		info.ExecutionDriver = "<not supported>" //nolint:staticcheck // ignore SA1019 (ExecutionDriver is deprecated)
-	}
-	if versions.LessThan(version, "1.39") {
-		if info.KernelVersion == "" {
-			info.KernelVersion = "<unknown>"
+		if versions.LessThan(version, "1.39") {
+			if info.KernelVersion == "" {
+				info.KernelVersion = "<unknown>"
+			}
+			if info.OperatingSystem == "" {
+				info.OperatingSystem = "<unknown>"
+			}
 		}
-		if info.OperatingSystem == "" {
-			info.OperatingSystem = "<unknown>"
+		if versions.GreaterThanOrEqualTo(version, "1.42") {
+			info.KernelMemory = false
 		}
-	}
-	if versions.GreaterThanOrEqualTo(version, "1.42") {
-		info.KernelMemory = false
-	}
+		return info, nil
+	})
 	return httputils.WriteJSON(w, http.StatusOK, info)
 }