Ich habe mich ja nun doch schon ein bisschen mit nebenläufiger Programmierung beschäftigt und gebe mir auch immer mühe Programme parallel, korrekt abarbeitbar zu schreiben. Aber so etwas wie heute habe ich noch nicht erlebt. Ich hatte ein Programm auf meinem Server was in unregelmäßigen Abständen ein Fehler geworfen hat. Bei mir auf meinem Laptop hat alles wunderbar funktioniert.
Erklärung des Codes
Um das Problem zu verstehen hier mal ein Minimalbeispiel welches das Problem recht gut aufzeigt:
### File test.rb
(0..20).map do |x|
Thread.new do
require 'test2.rb'
T.new
end
end.each do |t| t.join end
### File test2.rb
sleep 1
class T
end
Ich denke der Code dürfte recht einfach verständlich sein. Ich starte 20 Prozesse und includiere die Datei test2.rb
welche eine Sekunde wartet und dann die Klasse T definiert.
Anschließend erstelle ich ein Objekt von der Klasse T. Für die Leute die kein ruby können: require
includiert eine Datei nur einmal. Das heißt wenn die Datei schon includiert
wurde dann wird sie nicht noch einmal eingebunden.
Eigentlich sollte ja nichts schief gehen. Wenn test2.rb
noch nicht eingebunden wurde dann wird es eingebunden und wenn ein anderer Prozess die Datei schon geladen hat dann ist die Klasse T ja definiert
und es kann auch ein neues Objekt davon erstellt werden.
Das Problem
Nun zum Problem: Die Ruby Version auf meinem Server hat sich gedacht, wenn es sowieso in der Datei test2.rb
eine Sekunde warten muss kann es auch so lange die anderen Thread bedienen. Die
kommen nun auch an das require
aber da dieses ja schon aufgerufen wurde wird es nicht erneut eingebunden. Die Threads machen also mit T.new
weiter, was natürlich fehlschlägt weil class T
noch nicht ausgeführt wurde.
Die neueren Ruby-Versionen sind anscheinend so schlau und blockieren die anderen Threads währenddessen, so das dieser Fehler nicht mehr auftreten kann.
Ich finde dieser Fehler zeigt sehr schön wie gefährlich Nebenläufigkeit ist. In meinem Fall war der Fehler durch testen kaum auffindbar da er nur ca alle 20 Scriptaufrufe aufgetreten ist und dann auch
nur auf meinem Server. Nie im Leben hätte ich daran geglaubt das ein require
nicht Threadsafe ist. Zudem habe ich in meinem Code das require
gar nicht selbst aufgerufen sondern eine andere
Bibliothek. Somit war der Fehler weder durch Testen noch durch bloßes hinschauen auffindbar. Zum Glück hatte Aaron das notwendige Feingefühl und hat den Fehler finden können. THX!
Minimalbeispiel meines Programms
Der Vollständigkeit halber:
require 'open-uri'
(0..3).map do
Thread.new do
open("http://indeedgeek.de/")
end
end.each do |t| t.join end
Na wer findet den Bug? Wenn man den Code oft genug aufruft (einige tausend mal) bekommt man irgendwann zum Beispiel ein uninitialized constant Net::HTTP
Error da dies unter anderem von open(...)
includiert wird.
Comments:
(howto comment?)