From 854a3e3cc21dbc436a5979a31f014ebbae86f63f Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez Date: Sun, 15 Dec 2024 22:45:10 -0500 Subject: [PATCH 1/4] Add support for stopping timestamp updater --- time.go | 24 ++++++++++++++++++++---- time_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/time.go b/time.go index 4dfdfe4..53357bd 100644 --- a/time.go +++ b/time.go @@ -9,6 +9,7 @@ import ( var ( timestampTimer sync.Once timestamp uint32 + stopChan chan struct{} ) // Timestamp returns the current time. @@ -21,16 +22,31 @@ func Timestamp() uint32 { // which is much better for performance than determining it at runtime each time func StartTimeStampUpdater() { timestampTimer.Do(func() { - // set initial value atomic.StoreUint32(×tamp, uint32(time.Now().Unix())) + + stopChan = make(chan struct{}) go func(sleep time.Duration) { ticker := time.NewTicker(sleep) defer ticker.Stop() - for t := range ticker.C { - // update timestamp - atomic.StoreUint32(×tamp, uint32(t.Unix())) + for { + select { + case t := <-ticker.C: + atomic.StoreUint32(×tamp, uint32(t.Unix())) + case <-stopChan: + // Stop signal received, break the loop and exit goroutine + return + } } }(1 * time.Second) // duration }) } + +// StopTimeStampUpdater stops the currently running timestamp updater goroutine. +// This prevents leaking a goroutine if the updater is no longer needed. +func StopTimeStampUpdater() { + if stopChan != nil { + close(stopChan) + stopChan = nil + } +} diff --git a/time_test.go b/time_test.go index 2b41ff6..83cf935 100644 --- a/time_test.go +++ b/time_test.go @@ -30,6 +30,32 @@ func Test_TimeStampUpdater(t *testing.T) { checkTimeStamp(t, now+2, Timestamp()) } +func Test_StopTimeStampUpdater(t *testing.T) { + t.Parallel() + + // Start the timestamp updater + StartTimeStampUpdater() + + now := uint32(time.Now().Unix()) + checkTimeStamp(t, now, Timestamp()) + + // Wait for an increment + time.Sleep(1 * time.Second) + checkTimeStamp(t, now+1, Timestamp()) + + // Stop the updater + StopTimeStampUpdater() + + // Capture the timestamp after stopping + stoppedTime := Timestamp() + + // Wait again to see if it updates + time.Sleep(2 * time.Second) + + // It should not have changed since we've stopped the updater + require.Equal(t, stoppedTime, Timestamp(), "timestamp should not change after stopping updater") +} + func Benchmark_CalculateTimestamp(b *testing.B) { var res uint32 StartTimeStampUpdater() From f887276a74c679ceb7c7af537adb8c3a9fb0ed47 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez Date: Mon, 16 Dec 2024 00:12:42 -0500 Subject: [PATCH 2/4] Fix data race --- time.go | 14 +++++++------- time_test.go | 6 +----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/time.go b/time.go index 53357bd..1680d1b 100644 --- a/time.go +++ b/time.go @@ -24,8 +24,10 @@ func StartTimeStampUpdater() { timestampTimer.Do(func() { atomic.StoreUint32(×tamp, uint32(time.Now().Unix())) - stopChan = make(chan struct{}) - go func(sleep time.Duration) { + c := make(chan struct{}) + stopChan = c + + go func(localChan chan struct{}, sleep time.Duration) { ticker := time.NewTicker(sleep) defer ticker.Stop() @@ -33,17 +35,15 @@ func StartTimeStampUpdater() { select { case t := <-ticker.C: atomic.StoreUint32(×tamp, uint32(t.Unix())) - case <-stopChan: - // Stop signal received, break the loop and exit goroutine + case <-localChan: return } } - }(1 * time.Second) // duration + }(c, 1*time.Second) }) } -// StopTimeStampUpdater stops the currently running timestamp updater goroutine. -// This prevents leaking a goroutine if the updater is no longer needed. +// StopTimeStampUpdater stops the timestamp updater func StopTimeStampUpdater() { if stopChan != nil { close(stopChan) diff --git a/time_test.go b/time_test.go index 83cf935..08f9520 100644 --- a/time_test.go +++ b/time_test.go @@ -14,8 +14,6 @@ func checkTimeStamp(tb testing.TB, expectedCurrent, actualCurrent uint32) { } func Test_TimeStampUpdater(t *testing.T) { - t.Parallel() - StartTimeStampUpdater() now := uint32(time.Now().Unix()) @@ -31,8 +29,6 @@ func Test_TimeStampUpdater(t *testing.T) { } func Test_StopTimeStampUpdater(t *testing.T) { - t.Parallel() - // Start the timestamp updater StartTimeStampUpdater() @@ -50,7 +46,7 @@ func Test_StopTimeStampUpdater(t *testing.T) { stoppedTime := Timestamp() // Wait again to see if it updates - time.Sleep(2 * time.Second) + time.Sleep(3 * time.Second) // It should not have changed since we've stopped the updater require.Equal(t, stoppedTime, Timestamp(), "timestamp should not change after stopping updater") From 931653f8bb93c7cd539bd1706489db2b0620d369 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez Date: Mon, 16 Dec 2024 00:38:32 -0500 Subject: [PATCH 3/4] Simplify unit-test --- time.go | 1 + time_test.go | 11 ++--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/time.go b/time.go index 1680d1b..ddd8856 100644 --- a/time.go +++ b/time.go @@ -44,6 +44,7 @@ func StartTimeStampUpdater() { } // StopTimeStampUpdater stops the timestamp updater +// WARNING: Make sure to call this function before the program exits, otherwise it will leak goroutines func StopTimeStampUpdater() { if stopChan != nil { close(stopChan) diff --git a/time_test.go b/time_test.go index 08f9520..c16d9ab 100644 --- a/time_test.go +++ b/time_test.go @@ -32,21 +32,14 @@ func Test_StopTimeStampUpdater(t *testing.T) { // Start the timestamp updater StartTimeStampUpdater() - now := uint32(time.Now().Unix()) - checkTimeStamp(t, now, Timestamp()) - - // Wait for an increment - time.Sleep(1 * time.Second) - checkTimeStamp(t, now+1, Timestamp()) - // Stop the updater StopTimeStampUpdater() // Capture the timestamp after stopping stoppedTime := Timestamp() - // Wait again to see if it updates - time.Sleep(3 * time.Second) + // Wait TO see if it updates + time.Sleep(5 * time.Second) // It should not have changed since we've stopped the updater require.Equal(t, stoppedTime, Timestamp(), "timestamp should not change after stopping updater") From 373ac793628f4b92a7e7a865cc32e6c2f561eb30 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez Date: Mon, 16 Dec 2024 00:43:11 -0500 Subject: [PATCH 4/4] Update comment --- time_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/time_test.go b/time_test.go index c16d9ab..ca74ffa 100644 --- a/time_test.go +++ b/time_test.go @@ -38,9 +38,8 @@ func Test_StopTimeStampUpdater(t *testing.T) { // Capture the timestamp after stopping stoppedTime := Timestamp() - // Wait TO see if it updates + // Wait before checking the timestamp time.Sleep(5 * time.Second) - // It should not have changed since we've stopped the updater require.Equal(t, stoppedTime, Timestamp(), "timestamp should not change after stopping updater") }