rss feed articles activities all_comments

indeedgeek.de

Florian Eitel

Concurrency programming is hard

Fehler in Ruby's require

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?)