#!/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)
Hier ist eine Beispielausgabe auf 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!
Hier ist eine Beispielausgabe auf 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!
Wenn Sie dies auf JRuby ausführen, ist <strong i="5">@middleware_mutex</strong> ||= begin
nicht atomar und Sie führen nicht nur den Block mehrmals aus, sondern @middleware_mutex
wird tatsächlich mehrmals auf verschiedene Monitore gesetzt. Sieht so aus, als ob dies auch auf 2.6 passieren kann, da puts
einen Blockierungs-/Thread-Kontextwechsel einführen kann.
_Danke für die Analyse!_
Es ist ziemlich interessant zu sehen, dass ||=
selbst im MRT keine Einzelzuweisung garantiert.
@eregon Ja, das hat mich auch überrascht. Können wir die Anweisungen löschen, um zu sehen, was tatsächlich vor sich geht?
@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]
||=
ist also wie folgt:
if defined?(@middleware_mutex)
<strong i="17">@middleware_mutex</strong>
else
<strong i="18">@middleware_mutex</strong> = expression
end
Damit es in der MRT nicht außer Kraft gesetzt wird, benötigen wir:
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
Das ist natürlich mit Kosten verbunden, da es sich um eine zusätzliche Überprüfung vor der Zuweisung handelt.
Und der Block würde immer noch mehrmals ausgeführt, und der Versuch, dies automatisch zu beheben, würde wahrscheinlich zu einem Deadlock und hohem Overhead führen, sodass dieser Teil am besten dem Benutzercode überlassen wird.
Oh, ich stimme voll und ganz zu, dass wir das nicht automatisch beheben können, der Code ist aus Sicht der Thread-Sicherheit schrecklich kaputt und ||= atomar zu machen wird das nicht beheben.
Wow danke für die Analyse @ioquatix und wieder einmal ist deine Expertise rund um dieses Thema deutlich zu sehen.
Das ist, soweit ich weiß, ein ziemlich alter Code, der meiner Meinung nach speziell eingeführt wurde, um JRuby und andere parallele Ruby-Implementierungen zu unterstützen.
Heute unterstützen wir JRuby und andere Nicht-MRI-Rubine nicht mehr offiziell, hauptsächlich weil wir meiner Meinung nach nicht über das nötige Wissen verfügen, aber wir haben das einfach unberührt gelassen in der Hoffnung, dass es zumindest helfen würde.
Ich bin mir nicht sicher, wie ich das angehen soll, wenn Sie oder @eregon uns in die richtige Richtung weisen könnten, würde ich mich freuen, etwas Wissen zu erlangen und einen Blick darauf zu werfen 🤓
Dies ist auch ein Fehler auf CRuby. Ich kann eine PN einreichen.
Ich habe eine Lösung in # 1074 ausprobiert. Ich habe eine andere Idee, die das Konvertieren des Moduls MiddlewareRegistry
in eine Klasse beinhaltet. Also statt dessen:
Es könnte so etwas sein:
module Faraday
class Response
self.middleware_registry = MiddlewareRegistry.new(File.expand_path('response', __dir__),
raise_error: [:RaiseError, 'raise_error'],
logger: [:Logger, 'logger'],
)
Dies ist nicht auf Klassenlebenszyklus-Hooks angewiesen. Es ist ziemlich klar, dass hier MiddlewareRegistry
instanziiert wird, wenn der Ruby interpretiert wird.
class MiddlewareRegistry
def initialize
<strong i="14">@mutex</strong> = Monitor.new
end
end
Hilfreichster Kommentar
@ioquatix
||=
ist also wie folgt:Damit es in der MRT nicht außer Kraft gesetzt wird, benötigen wir:
Das ist natürlich mit Kosten verbunden, da es sich um eine zusätzliche Überprüfung vor der Zuweisung handelt.
Und der Block würde immer noch mehrmals ausgeführt, und der Versuch, dies automatisch zu beheben, würde wahrscheinlich zu einem Deadlock und hohem Overhead führen, sodass dieser Teil am besten dem Benutzercode überlassen wird.