Friday 2 June 2017

Golang gotcha #5: launched timers cannot be garbage collected till stopped or fired

You may run into this gotcha if you run timers with long timeouts in a tight for select loop. It is idiomatic to use a timer in order to provide a timeout on a channel receive. It is common to use time.After for this, because time.After is very easy to use as it provides a '<-chan Time', equivalent to 'NewTimer(d).C', but there is no way to stop it. And as the godoc says:

The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop() if the timer is no longer needed.

This example demonstrates such a leak when using time.After:
timer_leak.go
https://play.golang.org/p/XLY-isFtnn

This example demonstrates that the timers are not garbage collected even after the function they are launched in returns, as some may expect:
timer_leak_func.go
https://play.golang.org/p/W3rjZ5ziFb

This example demonstrates that stopping the timers resolves the leak:
timer_no_leak.go
https://play.golang.org/p/5CThl1JbaS

This may seem like an unnecessary optimisation but in the right circumstances, a tight for loop with a long lived timer, these can really add up.

Thursday 1 June 2017

Golang gotcha #4: Implicitly ignored return values

It is possible to implicitly ignore all values returned by a go function without any compilation or even vet errors. This is demonstrated in this playground example:

https://play.golang.org/p/V0fmYKADFx

Now, I am imagine that you are asking "Why is this ever allowed? Madness, madness, insanity and lies!" or something less dramatic. Well consider the fmt.Printf usage in the example and lets take a look at the fmt.Printf signature.


'if ignorance is bliss then knock the smile off my face' - Rage Against the Machine

 

Yes, we're only bloody, implicitly ignoring that too aren't we? You see, a fair amount of the standard library relies upon this behaviour so disallowing it would break backwards compatibility :(. Though I would argue that this implicit ignoring is bad practice and should not be explicitly utilised or encouraged when it comes to user defined functions, it is an easy avenue for bugs to creep in.

In-depth discussion here.

Golang gotcha #3: Accidentally shadowed variables




A common mistake that I have often seen cause exasperation to many programmers is that of accidental variable shadowing, this is one of the first things that I look for when asked to help debug misbehaving code. This occurs via misuse of the short variable declaration clause :=.

Let's recap declaration, assignment and what the shorthand does:

So the := clause is shorthand for a declaration and an assignment, where the type of the declaration is implicitly inferred from the assignment value. It is very useful and the lack of verbosity feels almost like you are using a dynamic language.

Now when does this get us into trouble? The problem occurs when we accidentally declare and assign to a new variable in a different scope rather than assign to an existing variable in an outer scope.

'Short cuts make long delays' - Frodo Baggins

https://play.golang.org/p/Pset590PA2

This example demonstrates an accidental variable shadowing bug in code used to determine the highest integer in a slice. The variable 'highest' is redeclared and assigned to in the scope of the if statement, shadowing the 'highest' variable declared in the scope of main. Whereas desired behaviour is assignment to the highest variable declared in the scope of the main function. Here we say that the variable highest is 'shadowed' as a result of this redeclaration. Try modifying line 14 of the code to 'highest = v' and note the change in behaviour.

Now, it is a good question as to why this is allowed, I believe that it is primarily to allow flexibility and to protect existing code from things like imports as explained here.

This is catchable by using go vet with the experimental option -shadow enabled.

Note that it is necessary to invoke vet via 'go tool vet' rather than 'go vet' in order to enable flags, see this issue.

For those interested, more in depth discussion can be seen here.