You might have heard that using blocking, waiting code, e.g., sleep in many
languages, is discouraged. Why?
Introduction to Sleep
Sleep is a blocking, waiting function present in some languages. Here’s
sleep in Ruby.
# task.rb
print "Starting task... "
sleep 10 # Simulating some activity...
puts "done."
The user experiences a printed output, with ten seconds of time between the ellipses and “done.”
$ ruby task.rb
Starting task... done.
It’s used when we want to wait for a task to be finished. Pretty cool, right?
Well, there are two big problems with sleep. First, it wastes time, and second,
it is a slippery slope. Let’s take a look.
Problems With Sleep: It Wastes Time
Consider the best case, where the function does its job. By that, I mean the time
we waited for, ten seconds, was long enough for the task to finish. In
the best case, the sleep slowed down the process artificially.
Why? Because the time we waited is almost always longer than the time it took for the task to complete. It took eight seconds; we waited ten. Or worse, it took two seconds, and we waited ten! Each time, we wait ten. Each time, we’re wasting time.
Who cares about a few seconds? We all should. Computing is a game of seconds. We all want computer tasks to run faster, when that’s easy to achieve.
On some teams, the unit test suite take 25, 30 minutes to run! That’s too long. You can bet there are some sleeps in there.
Problems With Sleep: It’s a Slippery Slope
Now, consider the worst case, when the task we’re waiting on eventually takes
longer than ten seconds. The argument to sleep then almost always gets
bigger. It’s a slippery slope (and a magic number, but I digress).
10 becomes 15. 15 becomes 30. Look at the Git history of a slow sleep,
like sleep 60, and you’ll often find that it started life as a sleep 10 or
sleep 20.
Once sleep is in the code, it’s easy to argue for bumping the number when
the stakes are high. We can’t deploy! The test suite is red! Just wait a
few more seconds.
Pretty soon, we’re sliding down a slippery slope, waiting longer and longer.
Solution: Find Something to Wait For
The solution is to find something to wait for.
A place we often see sleep is end-to-end tests.
visit('/')
sleep 5 # Sleep while the page loads
expect(page).to have_selector('.home-page')
Almost always, these environments provide a built-in interface to wait. This Capybara function will wait two seconds for the loading indicator to go away.
visit('/')
# No sleep ('till Brooklyn)
expect(page).to have_no_selector('.loading-indicator')
We can extend the wait time for the suite, the test, or the assertion. The tighter assertion usually is preferable.
expect(page).to
have_no_selector('.loading-indicator', wait: 5) # Waiting five seconds
If we don’t have a helper like that, we can build our own:
task_complete = false
while !task_complete
# Sleep for 1/10th of a second
sleep(0.1)
# Check the condition (attepmt to finish)
task_complete = check_task
end
Yes, this uses sleep. But it waits for a tenth of a second and then checks to
see if it’s done. That’s a lot smarter than waiting ten seconds.
Can We Ever Sleep?
So, can we ever sleep? My point is that I want us to view sleep skeptically. It’s a smell. There’s almost always something to wait for, and that’s always better than sleeping.
I’ll buy that there are rare cases where there’s nothing easy to wait for. It’s hard for me to think of such a case.
Conclusion
Don’t sleep.
Bigger picture, I think if we can’t find a thing to wait for, we might not understand the system well. Start a command, then wait ten seconds… what’s happening during those ten seconds?! Why does it take that long— ages for a computer system— to finish? The pro move is to find out.