Is go keyword always non-blocking?
When you look at your go code and see the go keyword, your first thought is like ok this is non-blocking because it’s prefixed with the go keyword. But sometimes this assumption may be wrong, let’s explore why.
Let say we have simple ChatBot
struct with a say
method:
1 |
|
Ok, next we tasked to make our chat bot to say something:
1 |
|
At some point we noticed that our chat bot is blocking real users, and decided to make it non-blocking. Solution was pretty straightforward as adding go
keyword in front of the method call.
1 |
|
Everybody was happy, until we received a new task to make our bot to say something other than hello and behave a human like. So next we implemented GeneratePhrase
method:
1 |
|
Now it’s time to integrate, and it looks like we can now replace hardcoded "Hello!"
with our new method! Done with it and it looks cool!
1 |
|
Deployed to prod and everybody was happy again. Well happiness is a volatile substance and indeed we noticed that our bot is blocking real users again. But wait theres is a go
keyword in front of the bot’s Say
method! Doesn’t it mean that the entire statement bot.Say(bot.GeneratePhrase())
should be async and non-blocking? Well let’s check.
1 |
|
Running this contrived example reveals, that entire go bot.Say(bot.GeneratePhrase())
statement takes 1s
to run and there is nothing wrong with the go
keyword. The go
keyword applies to the bot.Say
method only, but we are feeding the method’s parameter with another calculation bot.GeneratePhrase()
which takes place in the main’s goroutine. It turns out that above code is equivalent to the following:
1 |
|
Now it’s clear, why our bot was blocking while saying its phrase. To fix this we should wrap bot.Say(bot.GeneratePhrase())
statement with anonymous goroutine.
1 |
|
Conclusion: it’s very tempting to prefix a single line method with the go
keyword to make it async, however in that case we should pay attention to its parameters and watch how its arguments are populated. On the other hand we could avoid this bug from the beginning if wrapping the method with anonymous goroutine.