Problem Link to heading
You have a constructor function that creates an object with a background goroutine. You want the object to garbage collect itself even if people don’t explicitly close it. This isn’t possible since since the background goroutine is always running and pointing to your created object.
Solution Link to heading
Wrap the returned object and use a finalizer on the returned object to shut down the background goroutine.
Problem example Link to heading
Consider the Go statsd client go-statsd-client. The code to create a new buffered sender is below.
func NewBufferedSender(addr string, flushInterval time.Duration, flushBytes int) (Sender, error) {
simpleSender, err := NewSimpleSender(addr)
if err != nil {
return nil, err
}
sender := &BufferedSender{
flushBytes: flushBytes,
flushInterval: flushInterval,
sender: simpleSender,
buffer: senderPool.Get(),
shutdown: make(chan chan error),
}
sender.Start()
return sender, nil
}
The Start
function spawns a goroutine to periodically flush the buffering sender.
func (s *BufferedSender) Start() {
// write lock to start running
s.runmx.Lock()
defer s.runmx.Unlock()
if s.running {
return
}
s.running = true
s.bufs = make(chan *bytes.Buffer, 32)
go s.run()
}
Let’s now create and use a buffered sender to see what happens.
func Process() {
x := statsd.NewBufferedSender("localhost:2125", time.Second, 1024)
x.Inc("stat", 1, .1)
}
Initially x is pointed to by the main goroutine, but when we exit Process
BufferedSender is still around because the start
goroutine has not ended.
Inside Process Link to heading

Inside Process
Outside process Link to heading

When leaving Process
We’ve leaked BufferedSender
because we forgot to call Close
.
Solution example Link to heading
Consider the Go cache library go-cache. You’ll notice the Cache
type is
actually a wrapper.
type Cache struct {
*cache
// If this is confusing, see the comment at the bottom of New()
}
type cache struct {
defaultExpiration time.Duration
items map[string]Item
mu sync.RWMutex
onEvicted func(string, interface{})
janitor *janitor
}
When you create a new Cache
object it runs a janitor that points to the wrapped object, not the object that’s returned.
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
c := newCache(de, m)
// This trick ensures that the janitor goroutine (which--granted it
// was enabled--is running DeleteExpired on c forever) does not keep
// the returned C object from being garbage collected. When it is
// garbage collected, the finalizer stops the janitor goroutine, after
// which c can be collected.
C := &Cache{c}
if ci > 0 {
runJanitor(c, ci)
runtime.SetFinalizer(C, stopJanitor)
}
return C
}
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
}
c.janitor = j
go j.Run(c)
}
Let’s now draw what this looks like when you use it.
func Process() {
x := cache.New(time.Second, time.Minute)
}

Inside Process (with Janitor)

Outside Process (with Janitor)
The important difference here is that the Cache
object can be garbage collected, even if the cache
object cannot
yet. We set GC behavior on an object with SetFinalizer. The
stopJanitor function signals the background goroutine to stop.
runtime.SetFinalizer(C, stopJanitor)
...
...
func stopJanitor(c *Cache) {
c.janitor.stop <- true
}
With the background goroutine stopped, nothing is pointing to cache
either.

Cache with Janitor
At this point, the entire object can be garbage collected.
When to use Link to heading
Ideally you should defer to users of your library how they want to manage cleanly creating or shutting down
background processing. Go’s http.Serve is a great example of this. Notice how it’s not
func NewHTTPServer() *http.Server
, but instead the object is used and users can explicitly start (or stop) the
server when ready.
Despite this best practice, if you do want to control when background processing starts, you should still expose a
Close
function similar to the statsd client to allow users to immediately and explicitly free up a resource. If
you feel it’s difficult for users of your library to remember or abstract in an explicit close, you can add a wrapped
finalizer to ensure memory and goroutines you create can be eventually freed regardless of mistakes to forget to
call Close
.