2013-08-16 17 views
7

Sto provando a combattere alcuni casi di gara con il mio task manager in background. Essenzialmente, ho un oggetto Thing (già esistente) e gli assegno alcune proprietà, quindi lo salvo. Dopo che è stato salvato con le nuove proprietà, lo accodo in Resque, passando l'ID.Esegui codice rails dopo l'aggiornamento di un aggiornamento al database, senza after_commit

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

Il processo in background caricherà l'oggetto dal database utilizzando Thing.find(thing_id).

Il problema è che abbiamo riscontrato che Resque è così veloce nel riprendere il lavoro e nel caricare l'oggetto dall'ID, che carica un oggetto non aggiornato. Quindi all'interno del lavoro, chiamare thing.foo restituirà ancora "valore precedente" come 1/100 volte (non dati reali, ma non succede spesso).

Sappiamo che questo è un caso di gara, perché le rotaie torneranno da thing.saveprima che i dati è stato effettivamente commesso al database (PostgreSQL in questo caso).

Esiste un modo in Rails per eseguire solo il codice DOPO che un'operazione di database è stata eseguita? Essenzialmente, voglio assicurarmi che quando Resque carica l'oggetto, sta ottenendo l'oggetto più fresco. So che questo può essere ottenuto utilizzando un gancio after_commit sul modello Thing, ma non lo voglio lì. Ho solo bisogno che ciò avvenga in questo contesto specifico, non ogni volta che il modello è stato commutato nel DB.

risposta

5

È possibile inserire una transazione. Proprio come nell'esempio qui sotto:

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: c'è una gemma che chiama dopo la transazione, con questo si può risolvere il problema. Ecco il link: http://xtargets.com/2012/03/08/understanding-and-solving-race-conditions-with-ruby-rails-and-background-workers/

+0

Quindi, l'esecuzione di una transazione bloccherà l'esecuzione di 'ThingProcessor.queue_job (thing.id)' finché tutte le query nel blocco di transazione non si saranno impegnate? Pensavo che i blocchi di transazione si assicurassero che se si verificava un errore in qualsiasi azione del database all'interno del blocco, tutto veniva annullato. Una funzionalità di database. Non un rubino – Brian

+0

Credo di sì. Fai un tentativo e fammi sapere se funziona. –

+0

Impossibile trovare la documentazione di Rails che dice che un blocco di transazione blocca l'esecuzione. Qualcuno sa in modo definitivo? – Ben

0

Che dire di avvolgere un try intorno al transaction in modo che il lavoro è in coda soltanto in caso di successo della transazione?

+0

E 'thing.save' restituisce true solo dopo un salvataggio riuscito? ... Hai anche provato: 'ThingProcessor.queue_job (thing_id) if thing.save'. ? – user2669055

+2

No, 'thing.save' restituisce true dopo che Rails AR ha passato la richiesta al livello DB. Ecco perché i callback after_save possono avere problemi di race condition e perché c'è un callback after_commit che aspetta fino a dopo che il DB ha scritto e restituisce una conferma. Il problema con l'aggiunta di condizionali o blocchi di prova è che il lavoro non verrà messo in coda quando viene colpita la condizione della competizione. Più codice dovrebbe essere aggiunto per tornare indietro e provare a fare nuovamente il lavoro in coda più tardi. Questo aggiunge complessità. – Ben

+0

Scusa, penso che metà del mio commento precedente potrebbe essere sbagliato. È possibile che i blocchi di transazione * eseguano * l'esecuzione di blocchi e poiché le chiamate di "salvataggio" di Rails siano racchiuse in blocchi di transazione, la chiamata di salvataggio potrebbe non restituire effettivamente "true" fino a quando la transazione del database non viene completata correttamente. Quindi sarebbe possibile avvolgere in un condizionale. Ma non allevia il problema di dover trovare un modo per ri-accodare il lavoro. – Ben