소스 검색

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 년 전
부모
커밋
a1c9a686b0
2개의 변경된 파일37개의 추가작업 그리고 27개의 파일을 삭제
  1. 7 0
      api/server/router/system/system.go
  2. 30 27
      api/server/router/system/system_routes.go

+ 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)
 }