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