From 854a3e3cc21dbc436a5979a31f014ebbae86f63f Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <jgcalderonperez@protonmail.com>
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(&timestamp, 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(&timestamp, uint32(t.Unix()))
+			for {
+				select {
+				case t := <-ticker.C:
+					atomic.StoreUint32(&timestamp, 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 <jgcalderonperez@protonmail.com>
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(&timestamp, 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(&timestamp, 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 <jgcalderonperez@protonmail.com>
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 <jgcalderonperez@protonmail.com>
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")
 }