Skip to content

Commit

Permalink
Fix periodic interval after syncing
Browse files Browse the repository at this point in the history
  • Loading branch information
Daisuke Maki committed Sep 29, 2024
1 parent 8f3d81a commit f218783
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 17 deletions.
13 changes: 8 additions & 5 deletions backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
)

func (c *controller) adjustInterval(ctx context.Context, req adjustIntervalRequest) {
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: got adjust request (time until next check: %s)", time.Until(req.resource.Next())))
interval := roundupToSeconds(time.Until(req.resource.Next()))
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: got adjust request (current tick interval=%s, next for %q=%s)", c.tickInterval, req.resource.URL(), interval))

if c.tickInterval < interval {
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: no adjusting required (time to next check %s > current tick interval %s)", interval, c.tickInterval))
Expand Down Expand Up @@ -138,15 +138,18 @@ func (c *controller) loop(ctx context.Context, wg *sync.WaitGroup) {
c.handleRequest(ctx, req)
case t := <-c.check.C:
var minNext time.Time
var minInterval time.Duration = -1 * time.Second

Check failure on line 141 in backend.go

View workflow job for this annotation

GitHub Actions / lint

var-declaration: should omit type time.Duration from declaration of var minInterval; it will be inferred from the right-hand side (revive)
var dispatched int
for _, item := range c.items {
next := item.Next()
if minNext.IsZero() {
minNext = item.Next()
} else if next.Before(minNext) {
if minNext.IsZero() || next.Before(minNext) {
minNext = next
}

if interval := item.MinInterval(); minInterval < 0 || interval < minInterval {
minInterval = interval
}

if item.IsBusy() || next.After(t) {
continue
}
Expand All @@ -169,7 +172,7 @@ func (c *controller) loop(ctx context.Context, wg *sync.WaitGroup) {
// because we previously set ti to a small value for an immediate refresh.
// in this case, we want to reset it to a sane value
if c.tickInterval < time.Second {
c.SetTickInterval(defaultMinInterval)
c.SetTickInterval(minInterval)
c.traceSink.Put(ctx, fmt.Sprintf("httprc controller: resetting check intervanl to %s after forced refresh", c.tickInterval))
}
}
Expand Down
25 changes: 20 additions & 5 deletions httprc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestClient(t *testing.T) {

start := time.Now()
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=1")
w.Header().Set("Cache-Control", "max-age=2")
var version string
if time.Since(start) > 2*time.Second {
version = "2"
Expand Down Expand Up @@ -87,23 +87,38 @@ func TestClient(t *testing.T) {
{
URL: srv.URL + "/json/helloptr",
Create: func() (httprc.Resource, error) {
return httprc.NewResource[*Hello](srv.URL+"/json/helloptr", httprc.JSONTransformer[*Hello]())
r, err := httprc.NewResource[*Hello](srv.URL+"/json/helloptr", httprc.JSONTransformer[*Hello]())
if err != nil {
return nil, err
}
r.SetMinInterval(time.Second)
return r, nil
},
Expected: &Hello{Hello: "world"},
Expected2: &Hello{Hello: "world2"},
},
{
URL: srv.URL + "/json/hello",
Create: func() (httprc.Resource, error) {
return httprc.NewResource[Hello](srv.URL+"/json/hello", httprc.JSONTransformer[Hello]())
r, err := httprc.NewResource[Hello](srv.URL+"/json/hello", httprc.JSONTransformer[Hello]())
if err != nil {
return nil, err
}
r.SetMinInterval(time.Second)
return r, nil
},
Expected: Hello{Hello: "world"},
Expected2: Hello{Hello: "world2"},
},
{
URL: srv.URL + "/json/hellomap",
Create: func() (httprc.Resource, error) {
return httprc.NewResource[map[string]interface{}](srv.URL+"/json/hellomap", httprc.JSONTransformer[map[string]interface{}]())
r, err := httprc.NewResource[map[string]interface{}](srv.URL+"/json/hellomap", httprc.JSONTransformer[map[string]interface{}]())
if err != nil {
return nil, err
}
r.SetMinInterval(time.Second)
return r, nil
},
Expected: map[string]interface{}{"hello": "world"},
Expected2: map[string]interface{}{"hello": "world2"},
Expand Down Expand Up @@ -151,7 +166,7 @@ func TestClient(t *testing.T) {
})
}

time.Sleep(5 * time.Second)
time.Sleep(6 * time.Second)
for _, tc := range testcases {
t.Run("Lookup "+tc.URL, func(t *testing.T) {
r, err := ctrl.Lookup(ctx, tc.URL)
Expand Down
14 changes: 7 additions & 7 deletions resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,16 @@ func (r *ResourceBase[T]) determineNextFetchInterval(ctx context.Context, name s
traceSink := traceSinkFromContext(ctx)

if fromHeader > max {
traceSink.Put(ctx, fmt.Sprintf("%s > maximum interval, using maximum interval %s", name, max))
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s %s > maximum interval, using maximum interval %s", r.URL(), name, max))
return max
}

if fromHeader < min {
traceSink.Put(ctx, fmt.Sprintf("%s < minimum interval, using minimum interval %s", name, min))
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s %s < minimum interval, using minimum interval %s", r.URL(), name, min))
return min
}

traceSink.Put(ctx, fmt.Sprintf("Using %s (%d)", name, fromHeader))
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s Using %s (%s)", r.URL(), name, fromHeader))
return fromHeader
}

Expand All @@ -292,7 +292,7 @@ func (r *ResourceBase[T]) calculateNextRefreshTime(ctx context.Context, res *htt
// If constant interval is set, use that regardless of what the
// response headers say.
if interval := r.ConstantInterval(); interval > 0 {
traceSink.Put(ctx, fmt.Sprintf("Explicit interval set, using value %s", interval))
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s Explicit interval set, using value %s", r.URL(), interval))
return now.Add(interval)
}

Expand All @@ -301,7 +301,7 @@ func (r *ResourceBase[T]) calculateNextRefreshTime(ctx context.Context, res *htt
if err == nil {
maxAge, ok := dir.MaxAge()
if ok {
traceSink.Put(ctx, fmt.Sprintf("Cache-Control=max-age directive set (%d)", maxAge))
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s Cache-Control=max-age directive set (%d)", r.URL(), maxAge))
interval := r.determineNextFetchInterval(
ctx,
"max-age",
Expand All @@ -319,7 +319,7 @@ func (r *ResourceBase[T]) calculateNextRefreshTime(ctx context.Context, res *htt
if v := res.Header.Get(`Expires`); v != "" {
expires, err := http.ParseTime(v)
if err == nil {
traceSink.Put(ctx, fmt.Sprintf("expires header set (%s)", expires))
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s Expires header set (%s)", r.URL(), expires))
interval := r.determineNextFetchInterval(
ctx,
"expires",
Expand All @@ -332,7 +332,7 @@ func (r *ResourceBase[T]) calculateNextRefreshTime(ctx context.Context, res *htt
// fallthrough
}

traceSink.Put(ctx, "No cache-control/expires headers found, using minimum interval")
traceSink.Put(ctx, fmt.Sprintf("httprc.Resource.Sync: %s No cache-control/expires headers found, using minimum interval", r.URL()))
// Previous fallthroughs are a little redandunt, but hey, it's all good.
return now.Add(r.MinInterval())
}

0 comments on commit f218783

Please sign in to comment.