back home

How to call services asynchronously in Ruby?

That's a second part of the series where I present how to call multiple services at once in a different languages. It's not a production ready code but something that can be a fun exercise. Would you want to know why it's not production ready?

In Ruby there's timeout which I initially thought would solve the case. I was impressed first when I noticed it. But later realized what a piece of crap it is.

So I'm not going to use it in my example. I initially went to look for a better timeout. I found two different libraries solving some of the problems with the original.

Found frugal_timeout and terminator but they are useless for my example.

I then turned to concurrent-ruby and I felt at home. Because it's mostly Java API rewritten in Ruby ;-)

Check this out:

require 'concurrent'

class AggregationService
  MAX_WAIT_TIME = 5

  include Dry::Monads::Either::Mixin

  def initialize(clients = nil)
    @clients = clients || [ClientA.new, ClientB.new]
    @pool = Concurrent::ThreadPoolExecutor.new({
      min_threads: @clients.size,
      max_threads: Concurrent.processor_count
    })
  end

  def results
    lock = Concurrent::CountDownLatch.new(@clients.size)

    futures = @clients.map do |client|
      Concurrent::Future.execute(executor: @pool) do
        begin
          client.data()
        ensure
          lock.count_down
        end
      end
    end

    lock.wait(MAX_WAIT_TIME)

    results = futures.select { |f| f.fulfilled? }.map(&:value)

    {
      results: results.select { |either| either.success? }.map { |r| r.right },
      errors: results.select { |either| either.failure? }.map { |l| l.left }
    }
  end
end

It looks really similar to Java version.

The biggest difference is that I'm using a CountDownLatch to make sure code waits for threads to finish (with a timeout).

To spice things up I'm using Either from dry-monads.

Why Either? That's a great way to pass different data when the call succeeds or fails. It has also some fun methods and operators that let you combine different Eithers into another Either.

HTTP/1.1 200 OK
Content-Length: 27
Content-Type: application/json
X-Content-Type-Options: nosniff

{
    "errors": [], 
    "results": [
        2
    ]
}

http http://localhost:4567  0.20s user 0.05s system 4% cpu 5.298 total

As you can see it will wait for the result up for 5 seconds and return anything it got during that time.

Check the source code for details, it's a simple sinatra app.

This article is Part 2 in a 2-Part Series.