1 of 30

Golang Concurrency in Ruby

@sudhindraRao

2 of 30

Sudhindra Rao

  • 20 years in Software

  • Process Control Engineer for a few years

  • Rubyist since 2006

  • Golang Skeptic -> Embracer

  • Trainer

  • Developer of class from time to time

  • Current Status: Dev Manager @JFrog

(Uses Spaces not Tabs)

Follow me: @sudhindraRao

sudhindraRao.com

3 of 30

We are going to talk about Jim Carrey

  • Jim Carrey is famous - Truman Show, Dumb and Dumber and The Mask
  • Ruby has similar famous stories - Rails, Spotify, Github
  • Loved for their attributes - simplicity, expression of idea/emotion
  • Ruby/Jim Carrey both - Make people happy

4 of 30

Ruby and Concurrency

  • Threads and Thread pools
  • GVL or GIL
  • Good for I/O bound Concurrency
  • Ruby is built to make developers happy

5 of 30

Mechanisms for Protection

6 of 30

Our application

  • Is a FluentD plugin that process SIEM information

  • There is a list of vulnerabilities/violations

  • For each violation - get the details

  • Does this forever

  • Used to crash every 24-48 hours only with certain workloads

7 of 30

Code

8 of 30

Threads all over ...

def start

super

@running = true

@thread = Thread.new(&method(:run))

end

def shutdown

@running = false

@thread.join

super

end

9 of 30

while true

# Grab the batch of records

...

# limit max workers to thread count to prevent overloading xray.

thread_pool = Thread.pool(thread_count)

thread_pool.process {

for xray_violation_url in xray_violation_urls_list do

pull_violation_details(xray_violation_url)

end

}

thread_pool.shutdown

end

10 of 30

Even Matz feels this way

11 of 30

As we experience the crashes ...

12 of 30

I know how to do this in Golang...

13 of 30

And if we can do this in Ruby...

14 of 30

Concurrency in Go - A Primer

15 of 30

Goroutines

  • Concurrently executing

  • Similar to threads - But different

  • Much lighter memory footprint - and a growable stack

  • No identity to each routine - no thread-local storage

  • Cheaper to reschedule

16 of 30

Channels

  • Channel is a communication mechanism between goroutines

  • Unbuffered channels

  • Buffered channels

Do not communicate by sharing memory; instead, share memory by communicating.

17 of 30

Pipelines

  • The Go Programming Language - Page 227

  • Very powerful implementation by chaining operations with concurrency

  • No bookkeeping of threads

  • No bookkeeping of state

  • No ‘external’ bookkeeping for the system

18 of 30

concurrent-ruby gem

19 of 30

why concurrent-ruby gem

Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.

Concurrent Ruby makes one of the strongest thread safety guarantees of any Ruby concurrency library, providing consistent behavior and guarantees on all four of the main Ruby interpreters (MRI/CRuby, JRuby, Rubinius, TruffleRuby).

It is critical to remember, however, that Ruby is a language of mutable references. No concurrency library for Ruby can ever prevent the user from making thread safety mistakes

Concurrent Ruby is also the only Ruby library which provides a full suite of thread safe and immutable variable types and data structures.

20 of 30

Why not Ruby 3.0

Concurrency / Parallel

It’s multi-core age today. Concurrency is very important. With Ractor, along with Async Fiber, Ruby will be a real concurrent language. — Matz

Ractor (experimental)

Ractor is an Actor-model like concurrent abstraction designed to provide a parallel execution feature without thread-safety concerns.

Rails, Sidekiq already use concurrent-ruby

21 of 30

Abstractions Solve Problems

  • With concurrent-ruby you don’t think in terms of threads

  • But you use them - Promises, Futures, Channels

  • Provides higher level constructs to mimic Golang style concurrency

  • Thread safety is in-built

  • Takes advantage of implementations from other languages - Elixir, Clojure, Golang

22 of 30

Don’t forget the basics

  • Test Driven Development(TDD)

  • Malleable Code

  • Separation of Concerns - Identify responsibility

  • Encapsulation

23 of 30

Concurrent Ruby in Practice

def run

. . .

xray = Xray.new(@jpd_url, @username, @apikey, @wait_interval, @batch_size, @pos_file_path, router, @tag)

violations_channel = xray.violations(date_since)

xray.violation_details(violations_channel)

. . .

end

24 of 30

def violations(date_since)

violations_channel = Concurrent::Channel.new(capacity: @batch_size)

timer_task = Concurrent::TimerTask.new(execution_interval: @wait_interval, timeout_interval: 30) do

. . .

resp = get_violations(xray_json)

. . .

resp['violations'].each {|v| violations_channel = process(v, violations_channel) }

. . .

. . .

timer_task.execute

violations_channel

end

25 of 30

def violation_details(violations_channel)

violations_channel.each do |v|

Concurrent::Promises.future(v) do |v|

pull_violation_details(v['violation_details_url'])

pos_file = PositionFile.new(@pos_file_path)

pos_file.write(v)

end

end

end

26 of 30

it "creates a future for every violation" do

xray = Xray.new(@jpd_url, @username, @apikey, @wait_interval, @batch_size, @pos_file_path, @router, @tag)

(1..5).each do |i|

violations << i

end

promises = class_double("Concurrent::Promises").as_stubbed_const(:transfer_nested_constants => true)

expect(promises).to receive(:future).exactly(5).times

xray.violation_details(violations)

end

27 of 30

https://github.com/jfrog/fluent-plugin-jfrog-siem

28 of 30

References

29 of 30

THANK YOU! @sudhindraRao

30 of 30