Faraday: `middleware_mutex` é arriscado.

Criado em 23 out. 2019  ·  9Comentários  ·  Fonte: 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)

Aqui está a saída de exemplo em 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!

Aqui está a saída de exemplo no 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!

Comentários muito úteis

@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]

Então ||= é como:

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

Para que não seja substituído na ressonância magnética, precisarí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

Claro que isso tem um custo, pois é uma verificação extra antes de atribuir.

E o bloco ainda seria executado várias vezes, tentar corrigir isso automaticamente provavelmente levaria a deadlock e alta sobrecarga, então é melhor deixar essa parte para o código do usuário.

Todos 9 comentários

Se você executar isso no JRuby, <strong i="5">@middleware_mutex</strong> ||= begin não é atômico e você não apenas acaba executando o bloco várias vezes, @middleware_mutex na verdade é definido várias vezes para diferentes monitores. Parece que isso também pode acontecer no 2.6, já que puts pode introduzir bloqueio/troca de contexto de thread.

_Obrigado pela análise!_

É muito interessante ver que ||= não garante uma atribuição única, mesmo em ressonância magnética.

@eregon Sim, também fiquei surpreso com isso. Podemos despejar as instruções para ver o que realmente está acontecendo?

@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]

Então ||= é como:

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

Para que não seja substituído na ressonância magnética, precisarí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

Claro que isso tem um custo, pois é uma verificação extra antes de atribuir.

E o bloco ainda seria executado várias vezes, tentar corrigir isso automaticamente provavelmente levaria a deadlock e alta sobrecarga, então é melhor deixar essa parte para o código do usuário.

Oh, eu concordo totalmente que isso não é algo que podemos consertar automaticamente, o código está terrivelmente quebrado do ponto de vista de segurança de thread e fazer ||= atomic não vai consertar isso.

Uau, obrigado pela análise @ioquatix e mais uma vez sua experiência em torno deste tópico pode ser claramente vista.
Isso é, até onde eu sei, um pedaço de código bastante antigo que eu acho que foi introduzido especificamente para suportar JRuby e outras implementações de ruby ​​simultâneas.

Hoje, não apoiamos oficialmente JRuby e outros rubis que não sejam de ressonância magnética, principalmente porque acho que não temos o conhecimento necessário para fazê-lo, mas deixamos isso intocado na esperança de que pelo menos ajudasse.

Não tenho certeza de como lidar com isso, se você ou @eregon puderem nos apontar na direção certa, ficaria feliz em obter algum conhecimento e dar uma olhada 🤓

Este também é um bug no CRuby. Posso enviar um PR.

Eu tentei uma solução em # 1074. Eu tenho outra ideia que envolve converter o módulo MiddlewareRegistry em uma classe. Então ao invés disso:

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

Pode 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'],
    )

Isso não depende de ganchos de ciclo de vida de classe. Está bem claro que MiddlewareRegistry está sendo instanciado aqui quando o rubi está sendo interpretado.

class MiddlewareRegistry
  def initialize
    <strong i="14">@mutex</strong> = Monitor.new
  end
end
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

jeffb-stell picture jeffb-stell  ·  5Comentários

yusefu picture yusefu  ·  3Comentários

asf-stripe picture asf-stripe  ·  3Comentários

Lewiscowles1986 picture Lewiscowles1986  ·  4Comentários

ioquatix picture ioquatix  ·  4Comentários