From bb32c587b000722956ac41172b9d81b269b960d6 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 24 Jan 2024 18:22:36 +0000 Subject: [PATCH 1/4] fix(coderd/provisionerdserver): fix test flake in TestHeartbeat --- .../provisionerdserver/provisionerdserver.go | 18 ++++++++++++++---- .../provisionerdserver_test.go | 19 +++++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 9949e8dccf96e..adbab93abdf61 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -72,6 +72,9 @@ type Options struct { // The default function just calls UpdateProvisionerDaemonLastSeenAt. // This is mainly used for testing. HeartbeatFn func(context.Context) error + + // HeartbeatDone is used for testing. + HeartbeatDone chan struct{} } type server struct { @@ -183,6 +186,9 @@ func NewServer( if options.HeartbeatInterval == 0 { options.HeartbeatInterval = DefaultHeartbeatInterval } + if options.HeartbeatDone == nil { + options.HeartbeatDone = make(chan struct{}) + } s := &server{ lifecycleCtx: lifecycleCtx, @@ -213,7 +219,7 @@ func NewServer( s.heartbeatFn = s.defaultHeartbeat } - go s.heartbeatLoop() + go s.heartbeatLoop(options.HeartbeatDone) return s, nil } @@ -227,17 +233,21 @@ func (s *server) timeNow() time.Time { } // heartbeatLoop runs heartbeatOnce at the interval specified by HeartbeatInterval -// until the lifecycle context is canceled. -func (s *server) heartbeatLoop() { +// until the lifecycle context is canceled. Done is closed on exit. +func (s *server) heartbeatLoop(hbDone chan<- struct{}) { tick := time.NewTicker(time.Nanosecond) - defer tick.Stop() + defer func() { + close(hbDone) + }() for { select { case <-s.lifecycleCtx.Done(): s.Logger.Debug(s.lifecycleCtx, "heartbeat loop canceled") + tick.Stop() return case <-tick.C: if s.lifecycleCtx.Err() != nil { + tick.Stop() return } start := s.timeNow() diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 738e9da8dbd2f..2bea19c3a1681 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -104,6 +104,7 @@ func TestHeartbeat(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) heartbeatChan := make(chan struct{}) + heartbeatDone := make(chan struct{}) heartbeatFn := func(hbCtx context.Context) error { t.Logf("heartbeat") select { @@ -117,6 +118,7 @@ func TestHeartbeat(t *testing.T) { //nolint:dogsled // 。:゚૮ ˶ˆ ﻌ ˆ˶ ა ゚:。 _, _, _, _ = setup(t, false, &overrides{ ctx: ctx, + heartbeatDone: heartbeatDone, heartbeatFn: heartbeatFn, heartbeatInterval: testutil.IntervalFast, }) @@ -125,17 +127,9 @@ func TestHeartbeat(t *testing.T) { require.True(t, ok, "first heartbeat not received") _, ok = <-heartbeatChan require.True(t, ok, "second heartbeat not received") + // Cancel the context. This should cause heartbeatDone to be closed. cancel() - // Close the channel to ensure we don't receive any more heartbeats. - // The test will fail if we do. - defer func() { - if r := recover(); r != nil { - t.Fatalf("heartbeat received after cancel: %v", r) - } - }() - - close(heartbeatChan) - <-time.After(testutil.IntervalMedium) + <-heartbeatDone } func TestAcquireJob(t *testing.T) { @@ -1727,6 +1721,7 @@ type overrides struct { acquireJobLongPollDuration time.Duration heartbeatFn func(ctx context.Context) error heartbeatInterval time.Duration + heartbeatDone chan struct{} } func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisionerDaemonServer, database.Store, pubsub.Pubsub, database.ProvisionerDaemon) { @@ -1751,6 +1746,9 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi if ov.heartbeatInterval == 0 { ov.heartbeatInterval = testutil.IntervalMedium } + if ov.heartbeatDone == nil { + ov.heartbeatDone = make(chan struct{}) + } if ov.deploymentValues != nil { deploymentValues = ov.deploymentValues } @@ -1815,6 +1813,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi AcquireJobLongPollDur: pollDur, HeartbeatInterval: ov.heartbeatInterval, HeartbeatFn: ov.heartbeatFn, + HeartbeatDone: ov.heartbeatDone, }, ) require.NoError(t, err) From 6449c93eb8e95a66324f3c09b5d84a76e11faa98 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 25 Jan 2024 11:09:46 +0000 Subject: [PATCH 2/4] Revert "fix(coderd/provisionerdserver): fix test flake in TestHeartbeat" This reverts commit bb32c587b000722956ac41172b9d81b269b960d6. --- .../provisionerdserver/provisionerdserver.go | 18 ++++-------------- .../provisionerdserver_test.go | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index adbab93abdf61..9949e8dccf96e 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -72,9 +72,6 @@ type Options struct { // The default function just calls UpdateProvisionerDaemonLastSeenAt. // This is mainly used for testing. HeartbeatFn func(context.Context) error - - // HeartbeatDone is used for testing. - HeartbeatDone chan struct{} } type server struct { @@ -186,9 +183,6 @@ func NewServer( if options.HeartbeatInterval == 0 { options.HeartbeatInterval = DefaultHeartbeatInterval } - if options.HeartbeatDone == nil { - options.HeartbeatDone = make(chan struct{}) - } s := &server{ lifecycleCtx: lifecycleCtx, @@ -219,7 +213,7 @@ func NewServer( s.heartbeatFn = s.defaultHeartbeat } - go s.heartbeatLoop(options.HeartbeatDone) + go s.heartbeatLoop() return s, nil } @@ -233,21 +227,17 @@ func (s *server) timeNow() time.Time { } // heartbeatLoop runs heartbeatOnce at the interval specified by HeartbeatInterval -// until the lifecycle context is canceled. Done is closed on exit. -func (s *server) heartbeatLoop(hbDone chan<- struct{}) { +// until the lifecycle context is canceled. +func (s *server) heartbeatLoop() { tick := time.NewTicker(time.Nanosecond) - defer func() { - close(hbDone) - }() + defer tick.Stop() for { select { case <-s.lifecycleCtx.Done(): s.Logger.Debug(s.lifecycleCtx, "heartbeat loop canceled") - tick.Stop() return case <-tick.C: if s.lifecycleCtx.Err() != nil { - tick.Stop() return } start := s.timeNow() diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 2bea19c3a1681..738e9da8dbd2f 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -104,7 +104,6 @@ func TestHeartbeat(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) heartbeatChan := make(chan struct{}) - heartbeatDone := make(chan struct{}) heartbeatFn := func(hbCtx context.Context) error { t.Logf("heartbeat") select { @@ -118,7 +117,6 @@ func TestHeartbeat(t *testing.T) { //nolint:dogsled // 。:゚૮ ˶ˆ ﻌ ˆ˶ ა ゚:。 _, _, _, _ = setup(t, false, &overrides{ ctx: ctx, - heartbeatDone: heartbeatDone, heartbeatFn: heartbeatFn, heartbeatInterval: testutil.IntervalFast, }) @@ -127,9 +125,17 @@ func TestHeartbeat(t *testing.T) { require.True(t, ok, "first heartbeat not received") _, ok = <-heartbeatChan require.True(t, ok, "second heartbeat not received") - // Cancel the context. This should cause heartbeatDone to be closed. cancel() - <-heartbeatDone + // Close the channel to ensure we don't receive any more heartbeats. + // The test will fail if we do. + defer func() { + if r := recover(); r != nil { + t.Fatalf("heartbeat received after cancel: %v", r) + } + }() + + close(heartbeatChan) + <-time.After(testutil.IntervalMedium) } func TestAcquireJob(t *testing.T) { @@ -1721,7 +1727,6 @@ type overrides struct { acquireJobLongPollDuration time.Duration heartbeatFn func(ctx context.Context) error heartbeatInterval time.Duration - heartbeatDone chan struct{} } func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisionerDaemonServer, database.Store, pubsub.Pubsub, database.ProvisionerDaemon) { @@ -1746,9 +1751,6 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi if ov.heartbeatInterval == 0 { ov.heartbeatInterval = testutil.IntervalMedium } - if ov.heartbeatDone == nil { - ov.heartbeatDone = make(chan struct{}) - } if ov.deploymentValues != nil { deploymentValues = ov.deploymentValues } @@ -1813,7 +1815,6 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi AcquireJobLongPollDur: pollDur, HeartbeatInterval: ov.heartbeatInterval, HeartbeatFn: ov.heartbeatFn, - HeartbeatDone: ov.heartbeatDone, }, ) require.NoError(t, err) From 334a4a6abdea1ce4989e56957f64027322c56f08 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 25 Jan 2024 11:25:24 +0000 Subject: [PATCH 3/4] use testutil.RequireRecvCtx, rely on goleak for checking heartbeat goroutine --- .../provisionerdserver_test.go | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 738e9da8dbd2f..98f1ef2585a0d 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -67,7 +67,7 @@ func testUserQuietHoursScheduleStore() *atomic.Pointer[schedule.UserQuietHoursSc func TestAcquireJob_LongPoll(t *testing.T) { t.Parallel() - //nolint:dogsled // ૮・ᴥ・ა + //nolint:dogsled srv, _, _, _ := setup(t, false, &overrides{acquireJobLongPollDuration: time.Microsecond}) job, err := srv.AcquireJob(context.Background(), nil) require.NoError(t, err) @@ -76,7 +76,7 @@ func TestAcquireJob_LongPoll(t *testing.T) { func TestAcquireJobWithCancel_Cancel(t *testing.T) { t.Parallel() - //nolint:dogsled // ૮ ˶′ﻌ ‵˶ ა + //nolint:dogsled srv, _, _, _ := setup(t, false, nil) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() @@ -101,6 +101,7 @@ func TestAcquireJobWithCancel_Cancel(t *testing.T) { func TestHeartbeat(t *testing.T) { t.Parallel() + numBeats := 3 ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) heartbeatChan := make(chan struct{}) @@ -114,28 +115,17 @@ func TestHeartbeat(t *testing.T) { return nil } } - //nolint:dogsled // 。:゚૮ ˶ˆ ﻌ ˆ˶ ა ゚:。 + //nolint:dogsled _, _, _, _ = setup(t, false, &overrides{ ctx: ctx, heartbeatFn: heartbeatFn, heartbeatInterval: testutil.IntervalFast, }) - _, ok := <-heartbeatChan - require.True(t, ok, "first heartbeat not received") - _, ok = <-heartbeatChan - require.True(t, ok, "second heartbeat not received") - cancel() - // Close the channel to ensure we don't receive any more heartbeats. - // The test will fail if we do. - defer func() { - if r := recover(); r != nil { - t.Fatalf("heartbeat received after cancel: %v", r) - } - }() - - close(heartbeatChan) - <-time.After(testutil.IntervalMedium) + for i := 0; i < numBeats; i++ { + testutil.RequireRecvCtx(ctx, t, heartbeatChan) + } + // goleak.VerifyTestMain ensures that the heartbeat goroutine does not leak } func TestAcquireJob(t *testing.T) { From 7f42388005c917a38fd60f5c9521dc500079505a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 25 Jan 2024 11:48:01 +0000 Subject: [PATCH 4/4] testutil ctx with timeout --- coderd/provisionerdserver/provisionerdserver_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 98f1ef2585a0d..51880bf6088c7 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -102,8 +102,7 @@ func TestHeartbeat(t *testing.T) { t.Parallel() numBeats := 3 - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) + ctx := testutil.Context(t, testutil.WaitShort) heartbeatChan := make(chan struct{}) heartbeatFn := func(hbCtx context.Context) error { t.Logf("heartbeat") pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy