Concurrency in Ruby: Tools of the trade

Concurrency in Ruby: Tools of the trade

Concurrency has been a popular topic in the programming community in the latest decade and has received special attention in the Ruby community in the latest years. In this talk, José Valim will showcase the tools we have available in Ruby and in the community today, focusing on common pitfalls. This is a great opportunity to discuss why concurrency matters and how we could move forward.

7c12adb8b5521c060ab4630360a4fa27?s=128

Plataformatec

June 08, 2013
Tweet

Transcript

  1. @josevalim / Plataformatec Concurrency Ruby in

  2. About me

  3. •Started with Ruby in 2006 About me

  4. •Started with Ruby in 2006 •Open Source Contributor About me

  5. •Started with Ruby in 2006 •Open Source Contributor •Joined Rails

    Core in 2010 About me
  6. •Started with Ruby in 2006 •Open Source Contributor •Joined Rails

    Core in 2010 •Author of elixir-lang.org About me
  7. None
  8. Topics

  9. •Why concurrency? Topics

  10. •Why concurrency? •The problem today Topics

  11. •Why concurrency? •The problem today •Tools of the trade Topics

  12. Why concurrency?

  13. Server Server Server Server

  14. 50 cores $2600

  15. It is no longer about the future, it is about

    now
  16. Server

  17. In Ruby, we talk about threads

  18. We need our applications to be thread-safe

  19. The problem

  20. class User def self.current_user=(user) @@current_user = user end def self.current_user

    @@current_user end end
  21. post "/do_something" do User.current_user = user_from_request # some work Mailer.deliver_to(

    User.current_user) end
  22. 1. Assign current_user 2. Do some work 3. Get current_user

    to send an e-mail
  23. thread 1 current_ user thread 2

  24. thread 1 current_ user thread 2 jose

  25. thread 1 current_ user thread 2 jose jose

  26. thread 1 current_ user thread 2 jose jose jose

  27. thread 1 current_ user thread 2 jose jose jose jose

  28. thread 1 current_ user thread 2 jose jose matz jose

    jose
  29. thread 1 current_ user thread 2 jose jose matz matz

    jose jose
  30. thread 1 current_ user thread 2 jose jose matz matz

    jose jose jose
  31. thread 1 current_ user thread 2 jose jose matz matz

    jose jose jose jose
  32. thread 1 current_ user thread 2 jose jose matz matz

    jose jose jose jose jose
  33. thread 1 current_ user thread 2 jose jose matz matz

    jose jose jose jose jose jose
  34. thread 1 current_ user thread 2 jose jose matz matz

    jose jose jose jose jose jose
  35. User.current_user is shared mutable state (global)

  36. ActionMailer::Base.from ActionController::Base.logger Devise.password_length SimpleForm.form_options

  37. What is Ruby?

  38. Ruby is a programming language with different implementations

  39. Rails 2.2 threadsafe

  40. Thread-safety means different things for different implementations

  41. hash = {} 10.times do |i| Thread.new { hash[i] =

    "Hello #{rand}" } end Threadsafe?
  42. Thread-safe YARV Yes JRUBY No RBX 2.0 No Hashes

  43. global virtual machine lock (YARV) @cached[template] = File.read(template) static VALUE

    env_aset(VALUE obj, VALUE nm, VALUE val) { ... hash.c
  44. global virtual machine lock (YARV) @cached[template] = File.read(template) static VALUE

    env_aset(VALUE obj, VALUE nm, VALUE val) { ... hash.c
  45. C Code C2 C3 C4 C1

  46. C Code C2 C3 C4 C1

  47. C Code C2 C3 C4 C1

  48. C Code C2 C3 C4 C1

  49. C Code C2 C3 C4 C1

  50. Without the GVL, other Ruby implementations may try to change

    the same Hash at the same time, corrupting it
  51. • Class definitions • Instance variables • Class variables •

    Data structures ...
  52. Tools of the trade

  53. Thread locals

  54. class User def self.current_user=(user) @@current_user = user end def self.current_user

    @@current_user end end
  55. class User def self.current_user=(user) Thread.current[:cu] = user end def self.current_user

    Thread.current[:cu] end end
  56. Time.zone I18n.locale ActiveSupport::Notifications. instrumenter

  57. class Auth def initialize(callback) @callback = callback end def call(info)

    @user = user_from_info(info) res = @callback.call(info) update_user(res) # access @user res end end
  58. auth = Auth.new -> { puts "Hello" } auth.call(id: 13)

  59. class Auth def initialize(callback) @callback = callback end def call(info)

    @user = user_from_info(info) res = @callback.call(info) update_user(res) # access @user res end end
  60. class AuthMiddleware def initialize(callback) @callback = callback end def call(info)

    @user = user_from_info(info) res = @callback.call(info) update_user(res) # access @user res end end
  61. config.middleware.use( AuthMiddleware )

  62. Eager loading

  63. class MyApp < Sinatra::Base autoload :Broker, "my_app/broker" post "/content" do

    # ... MyApp::Broker.push(info) end end Threadsafe?
  64. module MyApp::Broker # ... def push do # ... end

    # ... end
  65. module MyApp::Broker # ... def push do # ... end

    # ... end
  66. module MyApp::Broker # ... def push do # ... end

    # ... end
  67. Let’s replace all autoloads by requires!

  68. module MyApp extend ActiveSupport::Autoload eager_autoload do autoload :Broker end end

    MyApp.eager_load!
  69. config.eager_load_namespaces << MyApp

  70. Mutex

  71. hash = {} 10.times do |i| Thread.new { hash[i] =

    "Hello #{rand}" } end
  72. None
  73. “There are heaps of non thread-safe usage of Hashes in

    Rails and the surrounding gems”
  74. Using hashes as a cache is an extremely common pattern

    in Ruby
  75. @cached[template] = File.read(template) Template lookup

  76. @controller_names[“posts”] = PostsController Router

  77. Helper def presenter_for(model) "#{model.class.name}Presenter". constantize.new(model) end presenter_for(Post.new) #=> PostPresenter<@post=...>

  78. @@p = Hash.new do |h, k| h[k] = "#{k}Presenter".constantize end

    def presenter_for(model) @@p[model.class.name].new(model) end
  79. hash = {} 10.times do |i| Thread.new { hash[i] =

    "Hello #{rand}" } end
  80. hash = {} mutex = Mutex.new 10.times do |i| Thread.new

    { mutex.synchronize { hash[i] = "Hello #{rand}" } } end
  81. Hash C2 C3 C4 C1

  82. Hash C2 C3 C4 C1

  83. Hash C2 C3 C4 C1

  84. Hash C2 C3 C4 C1

  85. Hash C2 C3 C4 C1

  86. None
  87. None
  88. None
  89. None
  90. Thread safe gem https://github.com/headius/thread_safe

  91. ThreadSafe::Hash.new ThreadSafe::Array.new

  92. Better tools of the trade

  93. Queues

  94. None
  95. live_assets#sse Manager SSE Subscriber live_assets#sse SSE live_assets#sse SSE Subscriber app/assets

    vendor/assets lib/assets Operating System Notifications
  96. class Manager def initialize @subscribers = [] end def subscribe(s)

    @subscribers << s end
  97. def unsubscribe(s) @subscribers.delete(s) end def notify(event) @subscribers.each do |s| s

    << event end end end
  98. live_assets#sse Manager SSE Subscriber live_assets#sse SSE live_assets#sse SSE Subscriber app/assets

    vendor/assets lib/assets Operating System Notifications
  99. class Manager attr_reader :queue def initialize @subscribers = [] @queue

    = Queue.new end def loop while message = @queue.pop event, *args = message send(event, *args) end end
  100. manager = Manager.new Thread.new { manager.loop } manager.queue << [:notify,

    :reloadCSS]
  101. C2 C3 C4 C1

  102. C2 C3 C4 C1

  103. C2 C3 C4 C1

  104. We need abstractions that can move us away from shared

    mutable state
  105. github.com/celluloid

  106. Conclusion

  107. We have learned about different tools to tackle thread safety

    issues
  108. thread.rb • Thread • Mutex • ConditionVariable • Queue •

    SizedQueue
  109. Gems • atomic • thread_safe

  110. The different semantics existing in different implementations makes concurrency in

    Ruby harder
  111. We need to push for better semantics at the same

    time we change how we think about concurrency
  112. Thank you! @josevalim / Plataformatec