#!/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)
Вот пример вывода на 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!
Вот пример вывода на 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!
Если вы запустите это на JRuby, <strong i="5">@middleware_mutex</strong> ||= begin
не будет атомарным, и вы не только в конечном итоге выполните блок несколько раз, @middleware_mutex
на самом деле несколько раз устанавливается для разных мониторов. Похоже, это также может произойти в версии 2.6, поскольку puts
может ввести блокировку/переключение контекста потока.
_Спасибо за анализ!_
Довольно интересно видеть, что ||=
не гарантирует однократного назначения даже на МРТ.
@eregon Да, меня это тоже удивило. Можем ли мы сбросить инструкции, чтобы увидеть, что на самом деле происходит?
@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]
Итак, ||=
выглядит так:
if defined?(@middleware_mutex)
<strong i="17">@middleware_mutex</strong>
else
<strong i="18">@middleware_mutex</strong> = expression
end
Чтобы это не переопределялось в МРТ, нам нужно:
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
Конечно, это стоит денег, так как это дополнительная проверка перед назначением.
И блок по-прежнему будет выполняться несколько раз, попытка исправить это автоматически может привести к взаимоблокировке и высоким накладным расходам, поэтому эту часть лучше оставить для пользовательского кода.
О, я полностью согласен, что это не то, что мы можем исправить автоматически, код ужасно нарушен из-за POV безопасности потоков, и создание ||= atomic не исправит это.
Вау, спасибо за анализ @ioquatix, и еще раз ясно виден ваш опыт в этой теме.
То есть, насколько мне известно, довольно старый фрагмент кода, который, я думаю, был введен специально для поддержки JRuby и других параллельных реализаций ruby.
Сегодня мы официально больше не поддерживаем JRuby и другие рубины, не относящиеся к МРТ, в основном потому, что я думаю, что нам не хватает необходимых знаний для этого, но мы просто оставили это нетронутым в надежде, что это, по крайней мере, поможет.
Я не совсем уверен, как с этим справиться, если бы вы или @eregon могли указать нам правильное направление, я был бы рад получить некоторые знания и взглянуть 🤓
Это также ошибка CRuby. Я могу отправить PR.
Я пробовал одно решение в #1074. У меня есть еще одна идея, связанная с преобразованием модуля MiddlewareRegistry
в класс. Итак, вместо этого:
Это может быть что-то вроде:
module Faraday
class Response
self.middleware_registry = MiddlewareRegistry.new(File.expand_path('response', __dir__),
raise_error: [:RaiseError, 'raise_error'],
logger: [:Logger, 'logger'],
)
Это не зависит от ловушек жизненного цикла класса. Совершенно ясно, что здесь создается экземпляр MiddlewareRegistry
, когда рубин интерпретируется.
class MiddlewareRegistry
def initialize
<strong i="14">@mutex</strong> = Monitor.new
end
end
Самый полезный комментарий
@ioquatix
Итак,
||=
выглядит так:Чтобы это не переопределялось в МРТ, нам нужно:
Конечно, это стоит денег, так как это дополнительная проверка перед назначением.
И блок по-прежнему будет выполняться несколько раз, попытка исправить это автоматически может привести к взаимоблокировке и высоким накладным расходам, поэтому эту часть лучше оставить для пользовательского кода.