From f21878377efff72688f100a862b3efc76fe52b0f Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sun, 29 Sep 2024 15:20:45 +0900 Subject: [PATCH] Fix periodic interval after syncing --- backend.go | 13 ++++++++----- httprc_test.go | 25 ++++++++++++++++++++----- resource.go | 14 +++++++------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/backend.go b/backend.go index a91f412..ef8ae01 100644 --- a/backend.go +++ b/backend.go @@ -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)) @@ -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 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 } @@ -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)) } } diff --git a/httprc_test.go b/httprc_test.go index 4b6831d..9fb39e1 100644 --- a/httprc_test.go +++ b/httprc_test.go @@ -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" @@ -87,7 +87,12 @@ 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"}, @@ -95,7 +100,12 @@ func TestClient(t *testing.T) { { 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"}, @@ -103,7 +113,12 @@ func TestClient(t *testing.T) { { 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"}, @@ -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) diff --git a/resource.go b/resource.go index cbc7b6b..597588c 100644 --- a/resource.go +++ b/resource.go @@ -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 } @@ -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) } @@ -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", @@ -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", @@ -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()) }