tickeryzer: static analyzer to report missing (*time.Ticker).Stop, which would have caused resource leaks

Cuong Manh Le
Orijtech Developers
4 min readJan 12, 2021

TL;DR: at Orijtech, Inc, we’ve developed a first of its kind static analyzer, “tickeryzer” that reports missing invocations of (*time.Ticker).Stop, which would have caused resource leaks of timers in your Go programs!

tickeryzer:: catch leaking timers

Introduction

For programs to be performant, conserving memory and CPU time is paramount! As more parts of a program are written, it becomes more expressive, but also the potential for logical errors aka bugs increases.

To time operations, the Go standard library’s time package provides 3 different instruments:

  • time.Sleep: this pauses the goroutine for the defined period
  • time.After: triggers a timer notification for convenience that’s discardable after it has been triggered. This is useful for example when timing racing/hedged operations such as send this request or end within this time
  • time.Ticker: this time provides a channel that delivers clock ticks at a regular interval. It is useful for example if you want to refresh a resource periodically, or say to clean up files at a specific interval

time.Ticker is the most reusable type given that if you want regular intervals, you can create a reliable source of ticks, and also use it in a channel for selects as well. It can be used for deterministic timed firing e.g. to monitor a resource exactly X seconds between regardless of what happens in between

Real life usage:

Say for example you run a web indexing service that monitors when websites change, but you’d only like to crawl front pages that have changed. To ensure proper history, you’d like regular archives, but also want to conserve resources. When a website’s frontend changes, we want to trigger a separate microservice that tails our logs, and this service will pick up on checksum changes and perform its magic. In Go, this program can be written for example like this…

webChecksumLead

and for example these are sample logs

and as you can see, within the past 4 seconds, the Google, and Reddit pages changed quite regularly, thus our next service will need to crawl them, but also to record in our timeseries, where we think the data might have changed.

Launch and aftermath:

After our new has been deployed, within 3 hours we notice huge memory spikes, too many files open, general slowness, and start investigating…

If you look closely you’ll see that we close files, close HTTP request bodies, but after a critical code review, we notice something….

mystery, suspense….

Real life motivation:

While auditing Go source code for one of our clients, my colleague found some instances of *time.Ticker created without corresponding calls to (*time.Ticker).Stop(), which causes the resource leaks, since when the *time.Ticker documentation explicitly states that:

time.NewTicker

We audited many code sites and found the problem to be quite common and easy to forget!

Oh noes, the problem is common!

He challenged me to write a static analyzer to detect this problem. Well, I liked his challenge and this spawned “tickeryzer”... with these goals:

Goals

Before writing the analyzer, we set some goals:

  • We only check if the ticker Stop method is not called in the same scope where the ticker was created.
  • We don’t check if the ticker is passed around via function argument, since when we haven’t seen the instance of it in real code.
  • We don’t care if the missing call is from “main.main”, since when main returning from main function terminate the whole process.

With these explicit goals, the challenge was set and the direction…

rising to the challenge!

Tickeryzer was born

We created tickeryzer, which you can get at github.com/orijtech/tickeryzer and it is generally available for all Go users in Go1.17 but trivially available for tools using golang.org/x/tools like gopls in your editors or first things first, let’s just get it

go get github.com/orijtech/tickeryzer/cmd/tickeryzer

In use:

Having downloaded it above, let’s examine a function which prints the current time every second:

The code above is simple, but it is buggy, because it doesn’t call “ticker.Stop”, so when “f” exits, the ticker associated resource is kept alive and recall the explicit warning in the time docs: time.NewTicker and (*time.Ticker).Stop?

Let’s try running “tickeryzer”:

and it complains line 10 in your source code, checking it:

Yeah, that’s where you defined the ticker, now fix it!

Mission accomplished.

We accomplished our goal

As mentioned above, we are making tickeryzer available for every Go programmer and we’ll include in Go1.17, as well as make it immediately available for anyone trying to include it in gopls. In Go1.17, when Go developers run go test they’ll automatically have the reports for such instances reported. As far as we know, we’ve examined all the static analyzers for Go and haven’t yet found anything like this… We are delighted to always improve the state of affairs for software!

Next steps:

Our mission is to build secure, high performant software and observability, and we are stewards and core contributors to the Go programming language, and also observability projects. We have even more planned; stay tuned to our channels, social media, share this with your friends too!

Thank you!

Thank you!

Cuong Manh Le and Emmanuel T Odeke

Engineering department

Orijtech, Inc.

--

--