Faraday: `middleware_mutex` es apresurado.

Creado en 23 oct. 2019  ·  9Comentarios  ·  Fuente: lostisland/faraday

#!/usr/bin/env ruby

# Taken from faraday internals
def middleware_mutex(&block)
    <strong i="5">@middleware_mutex</strong> ||= begin
        require 'monitor'
        puts "I'm making a monitor!"
        Monitor.new
    end
    puts @middleware_mutex.inspect
    @middleware_mutex.synchronize(&block)
end


threads = 10.times.collect do
    Thread.new do
        middleware_mutex do
            puts "I got the power!"
        end
    end
end

threads.each(&:join)

Aquí hay un ejemplo de salida en 2.6:

koyoko% rvm use 2.6          
Using /home/samuel/.rvm/gems/ruby-2.6.3
^[[A%                                                                                                                                                         koyoko% ./middleware_mutex.rb
I'm making a monitor!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055fb2217ec60 @mon_mutex=#<Thread::Mutex:0x000055fb2217ec10>, @mon_mutex_owner_object_id=47268548572720, @mon_owner=nil, @mon_count=0>
I got the power!
koyoko% ./middleware_mutex.rb
I'm making a monitor!
I'm making a monitor!
#<Monitor:0x000055f197e82388 @mon_mutex=#<Thread::Mutex:0x000055f197e82338>, @mon_mutex_owner_object_id=47248062026180, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e81eb0 @mon_mutex=#<Thread::Mutex:0x000055f197e81e60>, @mon_mutex_owner_object_id=47248062025560, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e81b90 @mon_mutex=#<Thread::Mutex:0x000055f197e81b40>, @mon_mutex_owner_object_id=47248062025160, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e81938 @mon_mutex=#<Thread::Mutex:0x000055f197e818e8>, @mon_mutex_owner_object_id=47248062024860, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e816e0 @mon_mutex=#<Thread::Mutex:0x000055f197e81690>, @mon_mutex_owner_object_id=47248062024560, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x000055f197e827c0 @mon_mutex=#<Thread::Mutex:0x000055f197e82590>, @mon_mutex_owner_object_id=47248062026720, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e81438 @mon_mutex=#<Thread::Mutex:0x000055f197e813e8>, @mon_mutex_owner_object_id=47248062024220, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e811e0 @mon_mutex=#<Thread::Mutex:0x000055f197e81190>, @mon_mutex_owner_object_id=47248062023920, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e80f88 @mon_mutex=#<Thread::Mutex:0x000055f197e80f38>, @mon_mutex_owner_object_id=47248062023620, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!
#<Monitor:0x000055f197e80d30 @mon_mutex=#<Thread::Mutex:0x000055f197e80ce0>, @mon_mutex_owner_object_id=47248062023320, @mon_owner=nil, @mon_count=0>
I got the power!

Aquí hay un ejemplo de salida en jruby:

koyoko% rvm use jruby        
Using /home/samuel/.rvm/gems/jruby-9.2.6.0
koyoko% ./middleware_mutex.rb
I'm making a monitor!
I'm making a monitor!
I'm making a monitor!
#<Monitor:0x7ea668b0 @mon_mutex=#<Thread::Mutex:0x3027cbd3>, @mon_owner=nil, @mon_count=0>
I got the power!
I'm making a monitor!I'm making a monitor!#<Monitor:0x53ca91a5 @mon_mutex=#<Thread::Mutex:0x57f50f04>, @mon_owner=nil, @mon_count=0>I'm making a monitor!
#<Monitor:0x1b5e79ce @mon_mutex=#<Thread::Mutex:0x159994d5>, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x67589eb3 @mon_mutex=#<Thread::Mutex:0x63671680>, @mon_owner=nil, @mon_count=1>#<Monitor:0x1b5e79ce @mon_mutex=#<Thread::Mutex:0x159994d5>, @mon_owner=nil, @mon_count=0>
I got the power!
#<Monitor:0x67589eb3 @mon_mutex=#<Thread::Mutex:0x63671680>, @mon_owner=nil, @mon_count=0>
I got the power!

#<Monitor:0x49c2c95a @mon_mutex=#<Thread::Mutex:0xcc04e06>, @mon_owner=nil, @mon_count=0>
I got the power!

I'm making a monitor!#<Monitor:0x1b5e79ce @mon_mutex=#<Thread::Mutex:0x159994d5>, @mon_owner=nil, @mon_count=0>
#<Monitor:0x6fa38df1 @mon_mutex=#<Thread::Mutex:0x756e0d98>, @mon_owner=nil, @mon_count=0>
I got the power!
I got the power!

#<Monitor:0x4a9f0f52 @mon_mutex=#<Thread::Mutex:0x600dfde7>, @mon_owner=nil, @mon_count=0>

I got the power!

I got the power!
I got the power!

Comentario más útil

@ioquatix

$ xsel -b | ruby --dump=insns
...
== disasm: #<ISeq:middleware_mutex@-:1 (1,0)-(9,3)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: 0, kw: -1@-1, kwrest: -1])
[ 1] block@0<Block>
0000 putnil                                                           (   2)[LiCa]
0001 defined                      instance-variable, :<strong i="7">@middleware_mutex</strong>, false
0005 branchunless                 14
0007 getinstancevariable          :<strong i="8">@middleware_mutex</strong>, <is:0>
0010 dup
0011 branchif                     42
0013 pop
0014 putself                                                          (   3)[Li]
0015 putstring                    "monitor"
0017 opt_send_without_block       <callinfo!mid:require, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0020 pop
0021 putself                                                          (   4)[Li]
0022 putstring                    "I'm making a monitor!"
0024 opt_send_without_block       <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0027 pop
0028 opt_getinlinecache           35, <is:1>                          (   5)[Li]
0031 getconstant                  :Monitor
0033 opt_setinlinecache           <is:1>
0035 opt_send_without_block       <callinfo!mid:new, argc:0, ARGS_SIMPLE>, <callcache>
0038 dup                                                              (   2)
0039 setinstancevariable          :<strong i="9">@middleware_mutex</strong>, <is:0>
0042 pop
0043 putself                                                          (   7)[Li]
0044 getinstancevariable          :<strong i="10">@middleware_mutex</strong>, <is:0>
0047 opt_send_without_block       <callinfo!mid:inspect, argc:0, ARGS_SIMPLE>, <callcache>
0050 opt_send_without_block       <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0053 pop
0054 getinstancevariable          :<strong i="11">@middleware_mutex</strong>, <is:0>          (   8)[Li]
0057 getblockparamproxy           block<strong i="12">@0</strong>, 0
0060 send                         <callinfo!mid:synchronize, argc:0, ARGS_BLOCKARG>, <callcache>, nil
0064 leave                                                            (   9)[Re]

Entonces ||= es como:

if defined?(@middleware_mutex)
  <strong i="17">@middleware_mutex</strong>
else
  <strong i="18">@middleware_mutex</strong> = expression
end

Para que no se anule en la resonancia magnética, necesitaríamos:

if defined?(@middleware_mutex)
  <strong i="22">@middleware_mutex</strong>
else
  result = expression
  # special bytecode to do `<strong i="23">@middleware_mutex</strong> = result unless defined?(@middleware_mutex)` atomically
end

Por supuesto eso tiene un costo ya que es un cheque extra antes de asignar.

Y el bloque aún se ejecutaría varias veces, tratar de arreglarlo automáticamente probablemente conduciría a un punto muerto y una sobrecarga alta, por lo que es mejor dejar esa parte en manos del código de usuario.

Todos 9 comentarios

Si ejecuta esto en JRuby, <strong i="5">@middleware_mutex</strong> ||= begin no es atómico y no solo termina ejecutando el bloque varias veces, sino que @middleware_mutex en realidad se configura varias veces en diferentes monitores. Parece que esto también puede suceder en 2.6 ya que puts puede introducir un cambio de contexto de bloqueo/subproceso.

_¡Gracias por el análisis!_

Es bastante interesante ver que ||= no garantiza una sola asignación, incluso en una resonancia magnética.

@eregon Sí, eso también me sorprendió. ¿Podemos volcar las instrucciones para ver qué está pasando realmente?

@ioquatix

$ xsel -b | ruby --dump=insns
...
== disasm: #<ISeq:middleware_mutex@-:1 (1,0)-(9,3)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: 0, kw: -1@-1, kwrest: -1])
[ 1] block@0<Block>
0000 putnil                                                           (   2)[LiCa]
0001 defined                      instance-variable, :<strong i="7">@middleware_mutex</strong>, false
0005 branchunless                 14
0007 getinstancevariable          :<strong i="8">@middleware_mutex</strong>, <is:0>
0010 dup
0011 branchif                     42
0013 pop
0014 putself                                                          (   3)[Li]
0015 putstring                    "monitor"
0017 opt_send_without_block       <callinfo!mid:require, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0020 pop
0021 putself                                                          (   4)[Li]
0022 putstring                    "I'm making a monitor!"
0024 opt_send_without_block       <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0027 pop
0028 opt_getinlinecache           35, <is:1>                          (   5)[Li]
0031 getconstant                  :Monitor
0033 opt_setinlinecache           <is:1>
0035 opt_send_without_block       <callinfo!mid:new, argc:0, ARGS_SIMPLE>, <callcache>
0038 dup                                                              (   2)
0039 setinstancevariable          :<strong i="9">@middleware_mutex</strong>, <is:0>
0042 pop
0043 putself                                                          (   7)[Li]
0044 getinstancevariable          :<strong i="10">@middleware_mutex</strong>, <is:0>
0047 opt_send_without_block       <callinfo!mid:inspect, argc:0, ARGS_SIMPLE>, <callcache>
0050 opt_send_without_block       <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0053 pop
0054 getinstancevariable          :<strong i="11">@middleware_mutex</strong>, <is:0>          (   8)[Li]
0057 getblockparamproxy           block<strong i="12">@0</strong>, 0
0060 send                         <callinfo!mid:synchronize, argc:0, ARGS_BLOCKARG>, <callcache>, nil
0064 leave                                                            (   9)[Re]

Entonces ||= es como:

if defined?(@middleware_mutex)
  <strong i="17">@middleware_mutex</strong>
else
  <strong i="18">@middleware_mutex</strong> = expression
end

Para que no se anule en la resonancia magnética, necesitaríamos:

if defined?(@middleware_mutex)
  <strong i="22">@middleware_mutex</strong>
else
  result = expression
  # special bytecode to do `<strong i="23">@middleware_mutex</strong> = result unless defined?(@middleware_mutex)` atomically
end

Por supuesto eso tiene un costo ya que es un cheque extra antes de asignar.

Y el bloque aún se ejecutaría varias veces, tratar de arreglarlo automáticamente probablemente conduciría a un punto muerto y una sobrecarga alta, por lo que es mejor dejar esa parte en manos del código de usuario.

Oh, estoy totalmente de acuerdo que esto no es algo que podamos arreglar automáticamente, el código está terriblemente roto desde el punto de vista de la seguridad de subprocesos y hacer ||= atómico no solucionará eso.

Wow, gracias por el análisis @ioquatix y, una vez más, se puede ver claramente su experiencia en este tema.
Eso es, hasta donde yo sé, una pieza de código bastante antigua que supongo que se introdujo específicamente para admitir JRuby y otras implementaciones simultáneas de Ruby.

Hoy en día, ya no admitimos oficialmente JRuby y otros rubíes que no son MRI, principalmente porque creo que carecemos del conocimiento necesario para hacerlo, pero lo dejamos intacto con la esperanza de que al menos ayude.

No estoy muy seguro de cómo abordar esto, si tú o @eregon pudieran indicarnos la dirección correcta, me encantaría obtener algo de conocimiento y echar un vistazo 🤓

Esto también es un error en CRuby. Puedo enviar un PR.

Probé una solución en el #1074. Tengo otra idea que consiste en convertir el módulo MiddlewareRegistry en una clase. Así que en lugar de esto:

https://github.com/lostisland/faraday/blob/d51a6c6979363c820d81a9166847ab09d30fae2b/lib/faraday/response.rb#L26 -L28

Podría ser algo como:

module Faraday
  class Response
    self.middleware_registry = MiddlewareRegistry.new(File.expand_path('response', __dir__),
      raise_error: [:RaiseError, 'raise_error'],
      logger: [:Logger, 'logger'],
    )

Esto no se basa en enlaces de ciclo de vida de clase. Está bastante claro que MiddlewareRegistry se está instanciando aquí cuando se interpreta el ruby.

class MiddlewareRegistry
  def initialize
    <strong i="14">@mutex</strong> = Monitor.new
  end
end
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

QuinnWilton picture QuinnWilton  ·  4Comentarios

mattmill30 picture mattmill30  ·  4Comentarios

mvastola picture mvastola  ·  4Comentarios

jeffb-stell picture jeffb-stell  ·  5Comentarios

ryanbyon picture ryanbyon  ·  3Comentarios