#!/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!
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:
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
Comentários muito úteis
@ioquatix
Então
||=
é como:Para que não seja substituído na ressonância magnética, precisaríamos:
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.