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.