Index: appengine/logdog/coordinator/service.go |
diff --git a/appengine/logdog/coordinator/service.go b/appengine/logdog/coordinator/service.go |
index 10f04358791cb8a02016bfe7417051da49b5c958..2e839735630a962294dff44ff085377b69babc2d 100644 |
--- a/appengine/logdog/coordinator/service.go |
+++ b/appengine/logdog/coordinator/service.go |
@@ -12,10 +12,12 @@ import ( |
"github.com/julienschmidt/httprouter" |
gaeauthClient "github.com/luci/luci-go/appengine/gaeauth/client" |
"github.com/luci/luci-go/appengine/logdog/coordinator/config" |
+ luciConfig "github.com/luci/luci-go/common/config" |
"github.com/luci/luci-go/common/errors" |
"github.com/luci/luci-go/common/gcloud/gs" |
"github.com/luci/luci-go/common/gcloud/pubsub" |
log "github.com/luci/luci-go/common/logging" |
+ "github.com/luci/luci-go/common/proto/logdog/svcconfig" |
"github.com/luci/luci-go/server/logdog/storage" |
"github.com/luci/luci-go/server/logdog/storage/bigtable" |
"github.com/luci/luci-go/server/middleware" |
@@ -43,6 +45,12 @@ type Services interface { |
// request. |
Config(context.Context) (*config.Config, error) |
+ // ProjectConfig returns the project configuration for the named project. |
+ // |
+ // The production instance will cache the results for the duration of the |
+ // request. |
+ ProjectConfig(context.Context, luciConfig.ProjectName) (*svcconfig.ProjectConfig, error) |
+ |
// Storage returns an intermediate storage instance for use by this service. |
// |
// The caller must close the returned instance if successful. |
@@ -76,7 +84,8 @@ type prodServicesInst struct { |
sync.Mutex |
// gcfg is the cached global configuration. |
- gcfg *config.Config |
+ gcfg *config.Config |
+ projectConfigs map[luciConfig.ProjectName]*cachedProjectConfig |
// archivalIndex is the atomically-manipulated archival index for the |
// ArchivalPublisher. This is shared between all ArchivalPublisher instances |
@@ -84,9 +93,6 @@ type prodServicesInst struct { |
archivalIndex int32 |
} |
-// Config returns the current instance and application configuration instances. |
-// |
-// After a success, successive calls will return a cached result. |
func (s *prodServicesInst) Config(c context.Context) (*config.Config, error) { |
s.Lock() |
defer s.Unlock() |
@@ -103,6 +109,50 @@ func (s *prodServicesInst) Config(c context.Context) (*config.Config, error) { |
return s.gcfg, nil |
} |
+// cachedProjectConfig is a singleton instance that holds a project config |
+// state. It is populated when resolve is called, and is goroutine-safe for |
+// read-only operations. |
+type cachedProjectConfig struct { |
+ sync.Once |
+ |
+ project luciConfig.ProjectName |
+ pcfg *svcconfig.ProjectConfig |
+ err error |
+} |
+ |
+func (cp *cachedProjectConfig) resolve(c context.Context) (*svcconfig.ProjectConfig, error) { |
+ // Load the project config exactly once. This will be cached for the remainder |
+ // of this request. |
+ // |
+ // If multiple goroutines attempt to load it, exactly one will, and the rest |
+ // will block. All operations after this Once must be read-only. |
+ cp.Do(func() { |
+ cp.pcfg, cp.err = config.ProjectConfig(c, cp.project) |
+ }) |
+ return cp.pcfg, cp.err |
+} |
+ |
+func (s *prodServicesInst) getOrCreateCachedProjectConfig(project luciConfig.ProjectName) *cachedProjectConfig { |
+ s.Lock() |
+ defer s.Unlock() |
+ |
+ if s.projectConfigs == nil { |
+ s.projectConfigs = make(map[luciConfig.ProjectName]*cachedProjectConfig) |
+ } |
+ cp := s.projectConfigs[project] |
+ if cp == nil { |
+ cp = &cachedProjectConfig{ |
+ project: project, |
+ } |
+ s.projectConfigs[project] = cp |
+ } |
+ return cp |
+} |
+ |
+func (s *prodServicesInst) ProjectConfig(c context.Context, project luciConfig.ProjectName) (*svcconfig.ProjectConfig, error) { |
+ return s.getOrCreateCachedProjectConfig(project).resolve(c) |
+} |
+ |
func (s *prodServicesInst) IntermediateStorage(c context.Context) (storage.Storage, error) { |
cfg, err := s.Config(c) |
if err != nil { |