2013-08-16 6 views
7

Ich versuche, einige Rennen Fällen mit meinem Hintergrund Task Manager zu kämpfen. Im Wesentlichen habe ich ein Thing Objekt (bereits existiert) und ordnen Sie ihm einige Eigenschaften zu und dann speichern Sie es. Nachdem es mit den neuen Eigenschaften gespeichert wurde, stelle ich es in Resque ein und übergebe die ID.Run Rails Code nach einem Update auf die Datenbank hat sich verpflichtet, ohne after_commit

thing = Thing.find(1) 
puts thing.foo # outputs "old value" 
thing.foo = "new value" 
thing.save 
ThingProcessor.queue_job(thing.id) 

Der Hintergrundjob lädt das Objekt mit Thing.find(thing_id) aus der Datenbank.

Das Problem ist, dass wir gefunden haben Resque ist so schnell beim Abholen des Jobs und Laden der Thing Objekt von der ID, dass es ein veraltetes Objekt lädt. Innerhalb des Jobs ruft der Aufruf thing.foo immer noch den "alten Wert" wie 1/100 (nicht echte Daten, aber es passiert nicht oft).

Wir wissen, dass dies ein Rennfall ist, weil Schienen von thing.savezurückkommen werden, bevor die Daten tatsächlich in die Datenbank (Postgresql in diesem Fall) festgeschrieben wurden.

Gibt es einen Weg in Rails, Code nur auszuführen, nachdem eine Datenbankaktion festgeschrieben wurde? Im Wesentlichen möchte ich sicherstellen, dass zu dem Zeitpunkt, zu dem Resque das Objekt lädt, es das frischeste Objekt erhält. Ich weiß, dass dies mit einem after_commit Haken auf dem Thing Modell erreicht werden kann, aber ich will es nicht dort. Ich brauche das nur in diesem einen spezifischen Kontext, nicht jedes Mal, wenn das Modell Commit in die DB geändert hat.

Antwort

5

Sie können auch eine Transaktion eingeben. Genau wie das folgende Beispiel:

transaction do 
    thing = Thing.find(1) 
    puts thing.foo # outputs "old value" 
    thing.foo = "new value" 
    thing.save 
end 
ThingProcessor.queue_job(thing.id) 

Update: es ist ein Juwel, die nach der Transaktion aufruft, damit Sie Ihr Problem lösen können. Hier ist der Link: http://xtargets.com/2012/03/08/understanding-and-solving-race-conditions-with-ruby-rails-and-background-workers/

+0

So blockiert die Ausführung einer Transaktion die Ausführung von 'ThingProcessor.queue_job (thing.id)', bis alle Abfragen im Transaktionsblock Commit haben? Ich dachte, dass Transaktionsblöcke nur sicherstellen, dass bei einem Datenbankfehler innerhalb des Blocks alles zurückgenommen wird. Eine Datenbankfunktion. Nicht ein Ruby-Feature – Brian

+0

Ich glaube es ist. Probieren Sie es aus und lassen Sie mich wissen, ob es funktioniert. –

+0

Keine Rails-Dokumentation gefunden, die besagt, dass ein Transaktionsblock die Ausführung blockiert. Wer weiß es definitiv? – Ben

0

Was ist eine try um die transaction Einwickeln so dass der Auftrag der Warteschlange besteht nur bei Erfolg der Transaktion?

+0

Und kehrt "ding.save" erst nach einer erfolgreichen Speicherung wieder zurück? ... Hast du auch versucht: 'ThingProcessor.queue_job (thing_id) if thing.save'. ? – user2669055

+2

Nein, 'thing.save' gibt true zurück, nachdem Rails AR die Anforderung an die DB-Schicht übergeben hat. Deshalb können After_Save-Callbacks Probleme mit Race-Conditions haben und warum gibt es einen After_commit-Callback, der wartet, bis die DB geschrieben hat und eine Bestätigung zurückgibt. Das Problem beim Hinzufügen von Bedingungen oder Testblöcken besteht darin, dass der Job nicht in die Warteschlange eingereiht wird, wenn die Race-Bedingung erreicht wird. Es müsste mehr Code hinzugefügt werden, um den Kreis zu schließen und den Job später erneut einzureihen. Dies erhöht die Komplexität. – Ben

+0

Entschuldigung, ich denke, der halbe vorherige Kommentar könnte falsch sein. Es ist möglich, dass Transaktionsblöcke * die Ausführung blockieren, und da Rails "save" -Aufrufe in Transaktionsblöcke eingeschlossen sind, gibt der Speicheraufruf möglicherweise erst dann "true" zurück, wenn die Datenbanktransaktion erfolgreich abgeschlossen wurde. So wäre es möglich, in eine Bedingung zu verpacken. Lindert jedoch nicht das Problem, einen Weg finden zu müssen, den Job neu zu ordnen. – Ben