Skip to content

Commit 6d836ad

Browse files
authored
Merge pull request #11 from mercari/otel-gcp-3
Optimize ForceSharedPolicy
2 parents a0be633 + 5ab0821 commit 6d836ad

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

server/handler/fetcher.go

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ package handler
1717
import (
1818
"context"
1919
"errors"
20+
"fmt"
2021
"net/http"
2122
"os"
23+
"sync"
2224
"time"
2325

2426
"github.com/google/go-github/v72/github"
@@ -39,8 +41,110 @@ type FetchedConfig struct {
3941
Path string
4042
}
4143

44+
type ConfigCache struct {
45+
mu sync.RWMutex
46+
expiry time.Time
47+
config *FetchedConfig
48+
}
49+
50+
func (cc *ConfigCache) GetOrUpdate(fn func() (*FetchedConfig, error)) (*FetchedConfig, bool, error) {
51+
const cacheUpdateTimeout = 30 * time.Second
52+
const cacheExpiryDuration = 1 * time.Minute
53+
54+
// Works both when the cache has not expired, and when another process is currently updating the cache (block on RLock).
55+
cached := func() *FetchedConfig {
56+
cc.mu.RLock()
57+
defer cc.mu.RUnlock()
58+
59+
if time.Now().Before(cc.expiry) {
60+
return cc.config
61+
}
62+
return nil
63+
}()
64+
if cached != nil {
65+
return cached, true, nil
66+
}
67+
68+
// Update
69+
cc.mu.Lock()
70+
defer cc.mu.Unlock()
71+
72+
timeoutCtx, cancel := context.WithTimeout(context.Background(), cacheUpdateTimeout)
73+
defer cancel()
74+
75+
var value *FetchedConfig
76+
var err error
77+
var updateDone = make(chan struct{}, 1)
78+
go func() {
79+
defer close(updateDone)
80+
value, err = fn()
81+
}()
82+
83+
select {
84+
case <-timeoutCtx.Done():
85+
return nil, false, errors.New("cache update timed out")
86+
case <-updateDone:
87+
if err != nil {
88+
return nil, false, err
89+
}
90+
cc.config = value
91+
cc.expiry = time.Now().Add(cacheExpiryDuration)
92+
return cc.config, false, nil
93+
}
94+
}
95+
4296
type ConfigFetcher struct {
43-
Loader *appconfig.Loader
97+
sharedConfigCache ConfigCache
98+
99+
Options PullEvaluationOptions
100+
Loader *appconfig.Loader
101+
}
102+
103+
func (cf *ConfigFetcher) configForSharedRepository(ctx context.Context, client *github.Client, owner string) (*FetchedConfig, error) {
104+
r, _, err := client.Repositories.Get(ctx, owner, *cf.Options.SharedRepository)
105+
if err != nil {
106+
return nil, fmt.Errorf("failed to get repository %s/%s: %w", owner, *cf.Options.SharedRepository, err)
107+
}
108+
109+
ref := r.GetDefaultBranch()
110+
file, _, _, err := client.Repositories.GetContents(ctx, owner, *cf.Options.SharedRepository, *cf.Options.SharedPolicyPath, &github.RepositoryContentGetOptions{
111+
Ref: ref,
112+
})
113+
if err != nil {
114+
return nil, fmt.Errorf("failed to get file %s/%s@%s:%s: %w", owner, *cf.Options.SharedRepository, ref, *cf.Options.SharedPolicyPath, err)
115+
}
116+
117+
content, err := file.GetContent()
118+
if err != nil {
119+
return nil, fmt.Errorf("failed to get content of file %s/%s@%s:%s: %w", owner, *cf.Options.SharedRepository, ref, *cf.Options.SharedPolicyPath, err)
120+
}
121+
122+
var pc policy.Config
123+
if err := yaml.UnmarshalStrict([]byte(content), &pc); err != nil {
124+
return nil, fmt.Errorf("failed to parse content of file %s/%s@%s:%s: %w", owner, *cf.Options.SharedRepository, ref, *cf.Options.SharedPolicyPath, err)
125+
}
126+
127+
fc := &FetchedConfig{
128+
Config: &pc,
129+
Source: fmt.Sprintf("%s/%s@%s", owner, *cf.Options.SharedRepository, ref),
130+
Path: *cf.Options.SharedPolicyPath,
131+
}
132+
return fc, nil
133+
}
134+
135+
func (cf *ConfigFetcher) ConfigForSharedRepository(ctx context.Context, client *github.Client, owner string) FetchedConfig {
136+
ctx, span := tracing.Tracer.Start(ctx, "ConfigFetcher.ConfigForSharedRepository")
137+
defer span.End()
138+
139+
config, cached, err := cf.sharedConfigCache.GetOrUpdate(func() (*FetchedConfig, error) {
140+
return cf.configForSharedRepository(ctx, client, owner)
141+
})
142+
span.SetAttributes(attribute.Bool("cache.hit", cached))
143+
144+
if err != nil {
145+
return FetchedConfig{LoadError: err}
146+
}
147+
return *config
44148
}
45149

46150
func (cf *ConfigFetcher) ConfigForRepositoryBranch(ctx context.Context, client *github.Client, owner, repository, branch string) FetchedConfig {
@@ -52,6 +156,10 @@ func (cf *ConfigFetcher) ConfigForRepositoryBranch(ctx context.Context, client *
52156
))
53157
defer span.End()
54158

159+
if cf.Options.ForceSharedPolicy {
160+
return cf.ConfigForSharedRepository(ctx, client, owner)
161+
}
162+
55163
retries := 0
56164
delay := 1 * time.Second
57165
for {

server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ func New(ctx context.Context, c *Config) (*Server, error) {
203203

204204
PullOpts: &c.Options,
205205
ConfigFetcher: &handler.ConfigFetcher{
206+
Options: c.Options,
206207
Loader: appconfig.NewLoader(
207208
policyPaths,
208209
appconfig.WithOwnerDefault(*c.Options.SharedRepository, sharedPolicyPaths),

0 commit comments

Comments
 (0)