Your SlideShare is downloading. ×
0
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Ruby thread safety first
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Ruby thread safety first

1,754

Published on

We rubyists historically haven’t been in the habit of thinking about concurrency but the reality is that our thread-unsafe code often works by sheer luck. There are different implementations of Ruby …

We rubyists historically haven’t been in the habit of thinking about concurrency but the reality is that our thread-unsafe code often works by sheer luck. There are different implementations of Ruby with their own semantics that can unearth challenging and unexpected concurrency bugs in our code. We have to become more accustomed to writing threadsafe code in order to anticipate these potential surprises, especially in light of the rise in popularity of JRuby.

I will discuss approaches to writing threadsafe code in this talk, with a specific focus on performance considerations and testing. I'll start by explaining some basic concurrency concepts, describe methods for handling shared mutable data, and touch on the subtleties of concurrency primitives (Mutex, ConditionVariable). Hair-raising, real-world bugs will be used throughout the presentation to illustrate specific concurrency issues and techniques for solving them.

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,754
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
10
Comments
0
Likes
5
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Text ! THREAD SAFETY FIRST Emily Stolfo Ruby Engineer at , Adjunct Faculty at Columbia @EmStolfo
  • 2. There is no such thing as thread-safe Ruby code.
  • 3. Ruby Engineer at Adjunct Faculty at Columbia Expert Emily Stolfo
  • 4. require 'set' members = Set.new threads = [] 200.times do |n| threads << Thread.new do if n % 2 == 0 members << n else members.first.nil? end end end threads.each(&:join) Demo code
  • 5. Inconsistent bug? 2.0 with 200 threads JRuby with 10 threads JRuby with 200 threads ✔ 𝙭 ✔
  • 6. 1. Concurrency in Ruby ! THREAD SAFETY FIRST 3. Testing concurrency 2. Writing thread-safe code
  • 7. CONCURRENCY IN RUBY
  • 8. There are different Ruby implementations.
  • 9. Threads are like music.
  • 10. GIL green thread native (OS) thread
  • 11. ruby 1.8 Threads and Ruby Instruments: OS threads Notes: green threads Conductor: GIL
  • 12. ruby 1.9+ Instruments: OS threads Notes: green threads Conductor: GIL Threads and Ruby
  • 13. JRuby Instruments: OS threads Notes: green threads Threads and Ruby
  • 14. Different Rubies? Different semantics.
  • 15. Our code sometimes works by sheer luck.
  • 16. You’re lucky your code hasn’t run on JRuby.
  • 17. You’re lucky there is a GIL.
  • 18. Example: MongoDB Ruby driver and Replica Sets
  • 19. MongoDB Replica Set Creation
  • 20. MongoDB Replica Set
  • 21. MongoDB Replica Set Failure
  • 22. MongoDB Replica Set Recovery
  • 23. MongoDB Replica Set Recovered
  • 24. Mutable shared state.
  • 25. class ReplicaSetClient def initialize @nodes = Set.new end ... def connect_to_nodes seed = valid_node seed.node_list.each do |host| node = Node.new(host) @nodes << node if node.connect end end def choose_node(type) @nodes.detect { |n| n.type == type } end end Ruby driver
  • 26. class ReplicaSetClient def initialize @nodes = Set.new end ... def connect_to_nodes seed = valid_node seed.node_list.each do |host| node = Node.new(host) @nodes << node if node.connect end end def choose_node(type) @nodes.detect { |n| n.type == type } end end Potential concurrency bug
  • 27. (RuntimeError) "can't add a new key into hash during iteration"
  • 28. We often use hashes as caches.
  • 29. Hashes and their derivatives are not thread-safe in JRuby. !
  • 30. How do we write this code thread-safely?
  • 31. WRITING THREAD-SAFE CODE
  • 32. Shared data: Avoid across threads.
  • 33. If you can’t avoid shared data, at least avoid shared mutable data.
  • 34. If you can’t avoid shared mutable data, use concurrency primitives.
  • 35. The top 2 concurrency primitives
  • 36. class ReplicaSetClient def initialize @nodes = Set.new @connect_mutex = Mutex.new end ... def connect_to_nodes seed = valid_node seed.node_list.each do |host| node = Node.new(host) @connect_mutex.synchronize do @nodes << node if node.connect end end end def choose_node(type) @connect_mutex.synchronize do @nodes.detect { |n| n.type == type } end end end 1. Mutex
  • 37. class ReplicaSetClient def initialize @nodes = Set.new @connect_mutex = Mutex.new end ... def connect_to_nodes seed = valid_node seed.node_list.each do |host| node = Node.new(host) @connect_mutex.synchronize do @nodes << node if node.connect end end end def choose_node(type) @connect_mutex.synchronize do @nodes.detect { |n| n.type == type } end end end 1. Mutex Use to isolate code that should be executed by at most one thread at a time. shared replica set state update
  • 38. A mutex is not magic.
  • 39. Avoid locking around I/O.
  • 40. class ReplicaSetClient def initialize @nodes = Set.new @connect_mutex = Mutex.new end ... def connect_to_nodes seed = valid_node seed.node_list.each do |host| node = Node.new(host) @connect_mutex.synchronize do @nodes << node if node.connect end end end def choose_node(type) @connect_mutex.synchronize do @nodes.detect { |n| n.type == type } end end end 1. Mutex network I/O
  • 41. class ReplicaSetClient def initialize @nodes = Set.new @connect_mutex = Mutex.new end ... def connect_to_nodes seed = valid_node seed.node_list.each do |host| node = Node.new(host) @connect_mutex.synchronize do @nodes << node end if node.connect end end def choose_node(type) @connect_mutex.synchronize do @nodes.detect { |n| n.type == type } end end end 1. Mutex network I/O
  • 42. Consider resources. Ex: Thundering herd
  • 43. Thundering herd n threads are woken up after waiting on something, but only 1 can continue. Waste of system resources to wake up n threads.
  • 44. Consider your system. Ex: Queued requests App server
  • 45. Think systemically. n threads are waiting to write to the replica set primary. The primary is flooded with requests when the replica set state is refreshed. App server
  • 46. 2. Condition Variable Use to communicate between threads.
  • 47. class Pool def initialize @socket_available = ConditionVariable.new ... end def checkout loop do @lock.synchronize do if @available_sockets.size > 0 socket = @available_sockets.next return socket end @socket_available.wait(@lock) end end end def checkin(socket) @lock.synchronize do @available_sockets << socket @socket_available. end end end Condition Variable broadcast Why is this a waste of system resources?
  • 48. class Pool def initialize @socket_available = ConditionVariable.new ... end def checkout loop do @lock.synchronize do if @available_sockets.size > 0 socket = @available_sockets.next return socket end @socket_available.wait(@lock) end end end def checkin(socket) @lock.synchronize do @available_sockets << socket @socket_available. end end end Condition Variable signal Only one thread can continue
  • 49. TESTING CONCURRENCY
  • 50. Testing 1. Test with different implementations. 2. Test with a ton of threads. 3. Use patterns for precision.
  • 51. require 'set' members = Set.new threads = [] 10.times do |n| threads << Thread.new do if n % 2 == 0 members << n else members.first.nil? end end end threads.each(&:join) Test with a ton of threads.
  • 52. require 'set' members = Set.new threads = [] 200.times do |n| threads << Thread.new do if n % 2 == 0 members << n else members.first.nil? end end end threads.each(&:join) Test with a ton of threads.
  • 53. Use synchronization methods if you need more precision. ex: rendezvous / barrier pattern
  • 54. Concurrency in Ruby Know your implementations. ! Know your concurrency primitives. ! Know your code. ”thread-safe JRuby 1.7.4 code” !
  • 55. Thanks Emily Stolfo @EmStolfo

×