Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I wait for multiple asynchronous operations to finish before sending a response in Ruby on Rails?

In some web dev I do, I have multiple operations beginning, like GET requests to external APIs, and I want them to both start at the same time because one doesn't rely on the result of the other. I want things to be able to run in the background. I found the concurrent-ruby library which seems to work well. By mixing it into a class you create, the class's methods have asynchronous versions which run on a background thread. This lead me to write code like the following, where FirstAsyncWorker and SecondAsyncWorker are classes I've coded, into which I've mixed the Concurrent::Async module, and coded a method named "work" which sends an HTTP request:

def index
  op1_result = FirstAsyncWorker.new.async.work
  op2_result = SecondAsyncWorker.new.async.work
  
  render text: results(op1_result, op2_result)
end

However, the controller will implicitly render a response at the end of the action method's execution. So the response gets sent before op1_result and op2_result get values and the only thing sent to the browser is "#".

My solution to this so far is to use Ruby threads. I write code like:

def index
  op1_result = nil
  op2_result = nil

  op1 = Thread.new do
    op1_result = get_request_without_concurrent
  end

  op2 = Thread.new do
    op2_result = get_request_without_concurrent
  end

  # Wait for the two operations to finish
  op1.join
  op2.join

  render text: results(op1_result, op2_result)
end

I don't use a mutex because the two threads don't access the same memory. But I wonder if this is the best approach. Is there a better way to use the concurrent-ruby library, or other libraries better suited to this situation?

like image 364
Matt Welke Avatar asked Nov 06 '16 21:11

Matt Welke


1 Answers

I ended up answering my own question after some more research into the concurrent-ruby library. Futures ended up being what I was after! Simply put, they execute a block of code in a background thread and attempting to access the Future's calculated value blocks the main thread until that background thread has completed its work. My Rails controller actions end up looking like:

def index
  op1 = Concurrent::Future.execute { get_request }
  op2 = Concurrent::Future.execute { another_request }

  render text: "The result is #{result(op1.value, op2.value)}."
end

The line with render blocks until both async tasks have finished, at which point result can begin running.

like image 67
Matt Welke Avatar answered Oct 20 '22 05:10

Matt Welke