Testing Asynchronous Threads in Ruby
Tags: ruby, learning, beginners, testing
Published: 2018-11-06 13:56:33

---
title: Testing Asynchronous Threads in Ruby
published: true
description: Using a mental model with a Batman metaphor we explain testing asynchonous
cover_image: https://blog.appsignal.com/images/blog/2018-11/testing-async-threads-in-ruby.jpg
tags: #ruby, #learning, #beginners, #testing
---

Threads and asynchronous environments are initially a bit tricky. Without a good mental model to organize interaction, it is easy to get into trouble and end up with unexpected results. On top of that, testing asynchronous code can be difficult without the right tools or test patterns.

Thinking about threads as people and shared objects as ‘things’ that can be owned helps to organize the working of a multithreaded system. In this episode we will go through an example to learn all about testing asynchronous Ruby code.

If you are using Rails or Rack or really any application that as a web browser front end you are in an asynchronous environment. The Rack `#call` is always called asynchronously. So whether you know it or not, there is a good chance you are already using multithreaded components.

## Testing: Trigger, collect and check

Testing an asynchronous callback API can be made synchronous by following a pattern of three steps; *trigger*, *collect*, and *check*. Think if each thread as a separate individual and objects as, well, things that can be owned by just one individual at a time.

We’ll use the example of Batman and his 7 different suits. Because that is a practical example and we can understand the importance of knowing whether all suits are with Alfred to be washed when you are about to run out and save the city.

## Example: Laundry day at the batcave

The example is Alfred washing Batman's suits. The SuitWashScheduler is a scheduler that invokes a callback for each washing event. The scheduler makes seven callbacks at one-second intervals starting one second after starting. The trigger is the
creation of the `SuitWashScheduler`.

```ruby
class SuitWashScheduler
  def initialize(cnt)
    Thread.new {
      cnt.times {
        sleep(1.0)
        yield
      }
    }
  end
end
```

## Collecting

Collecting results must be thread safe to avoid [race conditions](https://en.wikipedia.org/wiki/Race_condition). Any object shared across more than one thread has to be protected. Protection is a way of keeping track of the owner of an object. Only the owner can make changes or view the object. A suit can only be with Batman to use in fights, or with Alfred to be washed.

To stay friendly a thread (in the metaphor Batman or Alfred) only takes ownership for short times and then gives up the ownership. A `Mutex` is usually used to keep track of the owner. The `SuitwashScheduler` callback will own the result counter when the counter is incremented. The callback that is run in the `SuitWashScheduler` thread signals that all results have been received when the counter hits the target.

Writing the example starts with setting up some globals. In a real application the globals would be replaced by class or object attributes.

```ruby
$main_thread = Thread.current
$mu = Mutex.new
$count = 0
$target = 7
```

## Management and owners

The `$main_thread` and `$mu` are used to manage the threads and waiting for completion of the test while the `$target` and `$count` track the test results. Remember this is a trivial test so collecting and checking results has to be simple.

The test is started by creating a new instance of the `SuitWashScheduler`, giving the initializer the `$target` number of iterations. In this case, the 7 suits that need washing.
The block provided will be run in the `SuitWashScheduler` thread. For each iteration the `$count` is incremented and printed.

Looking ahead we realize that the main, test thread is going to be checking the`$count` also which means it will need ownership of the `$count` as well so a means of taking ownership of `$count` is needed. The `$mu` `Mutex` instance is the ownership token. In the block passed to the `SuitWashScheduler.new` call a `$mu.synchronize` block takes ownership long enough to set the `$count` and check the results. More on the results check in a moment.

```ruby
SuitWashScheduler.new($target) {
  $mu.synchronize {
    $count += 1
    puts $count
    $main_thread.wakeup if $target <= $count
  }
}
```

## Check: are all suits done?

Back at the main thread we need to wait for the test to finish. Batman needs to wait before all 7 suits are done. There are two conditions to check for; the tests updates the `$count` as expected or Batman gets bored waiting for the test to finish and times out. Before checking the `$count` to see if it has reached the `$target`, the ownership of `$count` is needed. Just like in the block for the `SuitWashScheduler` a call to `$mu.synchronize` is used.

But that can't be right, if we lock up the main thread how can the `SuitWashScheduler` thread ever change the `$count`? Luckily for us there is a neat trick that takes care of this. The `Mutex` class has a `#sleep` method that gives up ownership and waits until either it times out or is woken. Once woken either through the timeout or a `#wakeup` call to the main thread `$mu` attempts to take ownership again before continuing. Once ownership has been achieved the results can be checked and a determination of the pass or fail state of the test can be made.

```ruby
$mu.synchronize {
  $mu.sleep($target + 1)
  if $target != $count
    puts 'FAILED'
  else
    puts 'Passed! All suits are washed and clean'
  end
}
```

If you want to get deeper into this, you can make the example a bit more interesting by trying to create multiple schedulers and see how the Mutex keep the `$count` changes from colliding. As if Batman sends some suits to Alfred to be washed, and some others to the dry cleaners. Make sure to change the logic to make sure the `$target` check is a total of all the expected yields.

## Roundup

Working with threads and asynchronous environment gets easier with the right mental model.
In this post we used people as a metaphor for threads and physical objects (suits) as metaphor for shared objects, that can only be owned by one thread or person at a time. We think this way to abstract makes it easier to understand and remember.

We hope the examples with make you remember the mechanisms of async, but we hope the image of Batman running out while all his suits are being washed won’t stick with you for too long.

PS If you are done with all the batman metaphors on the blog, let us know.

*[Peter Ohler](http://www.ohler.com) creates quite bit of a high performance code, and writes about it too, every now and then. He made the Agoo gem, which is a pretty cool high performance HTTP server.*