Misunderstanding Go Timer Resets
Tags: go, programming, coding, learning
Published: 2024-06-10 07:40:51

---
title: Misunderstanding Go Timer Resets
published: true
description: 
tags: golang, programming, coding, learning
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-06-10 03:36 +0000
---

My teammate shared a snippet of code with me the other day which led to a fun rabbit hole.

## TL;DR:
Remember to drain your timer channel before resetting it if the reset is not a result of the timer firing a notification (e.g. you read from the  channel)

##What is a Go Timer?
As the name implies, a timer waits a duration of time before firing an event (sending a signal to a channel).

Timers can be stopped, and reset. Just like in real life!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62ksi2lskirbs5ajhl0k.jpg)

Here's an example:
```golang
t := time.NewTimer(10 * time.Second)

fmt.Println("Wait for 10 seconds for the timer to send a signal")
<- t.C
fmt.Println("It's been 10 seconds!)
```

Of course, you could always just use `time.Sleep(10 * time.Second)` in the above example. Unlike sleep, you can stop the Timer before the duration has been met.

Example:
```golang
t := time.NewTimer(10 * time.Second)

go func() {
   <- t.C
   fmt.Println("It's been 10 seconds!")
}()

fmt.Println("Never mind, don't wait 10 seconds for the timer")
t.Stop()

time.Sleep(15 * time.Second)
fmt.PrintLn("Slept for 15 seconds!")
```

And just like a kitchen timer, you can reset it:
```golang
t := time.NewTimer(10 * time.Second)

for {
    select {
    case <- t.C:
        // If you're feeling spicy you could reset with a random duration
        t.Reset(5 * time.Second)
    default:
        time.Sleep(time.Second)
    }
}
```

##Everything makes sense until it doesn't

So, I thought I had Timers understood until my teammate sent me this snippet of code:

```golang
package main

import (
	"fmt"
	"time"
)

func main() {
	signal := make(chan int)
	go func() {
		for {
			signal <- 1 
		}
	}()

	t := time.NewTimer(time.Second)
	for {
		select {
		case <-signal:
			time.Sleep(2 * time.Second)
			t.Reset(time.Second)
			fmt.Print("1")
		case <-t.C:
			fmt.Print("!")
		default:
			fmt.Print(".")
		}
	}
}
```
Maybe you're an expert in timers, channels, and select blocks and can see the bug here.

At first glance, I would never expect to see `!` printed. But here is example output:

```
$ go run main.go
.111!1!1!1111
```

Shouldn't the timer reset prevent the `!` branch from ever getting executed? Have I been misunderstanding and using reset incorrectly?

Looking at the documentation for Reset, this is near the top:

> For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with **drained channels**.

D'oh! Even if the timer has been reset, it doesn't change the fact that it has already fired and so the channel contains an event! And select will pick a branch "randomly" if more than one branch is ready.

And just a few lines down, we are given instructions on how to use reset:

> If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly.
> **If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained:**

```golang
if !t.Stop() {
    <-t.C
}
t.Reset(d)
```

##Putting it all together, correctly

Since we did not receive anything from the timer channel, we need to check if the timer has already went off, and if so we should drain the channel before resetting it. Changing the snippet as so:
```golang
for {
    select {
    case <-signal:
        time.Sleep(2 * time.Second)
        if !t.stop() {
            // drain the channel
            <-t.C
        }
        t.Reset(1 * time.Second)
        fmt.Printf("1")
    case <-t.C:
        fmt.Printf("!")
    default:
        fmt.Print(".")
    }
}
```

Now, all shall be well!

Example output:

```
$ go run main.go
.111111111111111111111
```

### Resources:
- https://gobyexample.com/timers
- https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/time/sleep.go;l=46-53
https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/time/sleep.go;l=104-105

---

As part of this little journey, we also discovered running this snippet on a MacBook Pro 2019 (intel), vs Ubuntu on WSL (intel) yielded consistently different outputs which I may or may not write about once I have all the facts straight.

Some things I thought about:
- MacOS vs Ubuntu? MacOS vs Windows? Is this even relevant?
- ✨schedulers✨
- Goroutines are not (os) threads. Repeat after me "goroutines are not (os) threads"