Skip to content

Instantly share code, notes, and snippets.

@camertron
Last active April 18, 2019 16:36
Show Gist options
  • Select an option

  • Save camertron/b3bf115726370319b841 to your computer and use it in GitHub Desktop.

Select an option

Save camertron/b3bf115726370319b841 to your computer and use it in GitHub Desktop.
Concurrent programming in Ruby

Introduction

You may have heard before that Ruby isn't a concurrent language. In other words, it's not capable of doing two things at once. For the most part, you're right - the canonical version of Ruby that we use at Lumos (called MRI) locks the interpreter to a single thread of execution. This means you can never truly run a task "in the background" within the same process. MRI relaxes the lock however for certain IO operations, including file operations and network activity. The Ruby interpreter will "unlock" itself whenever it detects an IO operation, meaning other threads get to run. Pretty cool :)

Since Ruby does support at least some level of concurrency, it would be cool if we could use it in our apps. There are a couple of great Ruby concurrency libraries out there - namely Celluloid and concurrent-ruby. I'm going to talk about concurrent-ruby in this gist because it's the one I know best.

Promises

Lots of other languages have concurrency primitives built-in, and one of the most popular is the "promise". Promises encapsulate a unit of deferred execution - in other words, they execute a ruby block in a separate thread. Promises can be in a number of different states, but when the block finishes executing, they get marked as either "fulfilled" (succeeded) or "rejected" (failed).

Let's use concurrent-ruby's Promise class to execute a web request in the background:

require 'net/http'
require 'concurrent'

promise = Concurrent::Promise.execute do
  Net::HTTP.get('http://google.com')
end

promise.state  # => :unscheduled
sleep 0.1
promise.state  # => :pending
sleep 2
promise.state  # => :fulfilled

Calling promise.value will block (halt your program) until the future is done executing.

Instead of waiting for the future to finish, which could take some time, you can chain another operation:

promise.then do |response|
  # the `response` block argument is the response object from our Net::HTTP call above
  puts response.body
end

What happens if an error gets raised inside our promise?

promise = Concurrent::Promise.execute do
  raise 'jelly beans!'
end

promise.then { puts 'next!' }

promise.state  # => :rejected

In this scenario, "next" never gets written to the console. When chaining promises, it's important to remember that a then block wont'get executed if its parent gets rejected.

Conclusion

Concurrent-ruby contains a bunch more awesome tools to make concurrency easier in Ruby. Go check it out!

@alan-andrade

Copy link
Copy Markdown

💭 Sweet !!!

@alan-andrade

Copy link
Copy Markdown

Interesting to see how Ruby is adopting a Javascript technique.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment