2013-08-16 32 views
7

Tôi đang cố gắng chống lại một số trường hợp đua với trình quản lý tác vụ nền của tôi. Về cơ bản, tôi có một đối tượng Thing (đã tồn tại) và gán cho nó một số thuộc tính, sau đó lưu nó. Sau khi nó được lưu với các thuộc tính mới, tôi xếp hàng nó trong Resque, chuyển vào ID.Chạy mã đường ray sau khi cập nhật cơ sở dữ liệu đã cam kết, không có after_commit

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

Công việc nền sẽ tải đối tượng từ cơ sở dữ liệu bằng cách sử dụng Thing.find(thing_id).

Vấn đề là chúng tôi đã tìm thấy Resque quá nhanh khi chọn công việc và tải đối tượng Thing từ ID, rằng nó tải một đối tượng cũ. Vì vậy, trong công việc, gọi thing.foo sẽ vẫn trả lại "giá trị cũ" như 1/100 lần (không phải dữ liệu thực, nhưng nó không xảy ra thường xuyên).

Chúng tôi biết đây là trường hợp cuộc đua, vì đường ray sẽ trở về từ thing.savetrước khi dữ liệu thực sự được cam kết với cơ sở dữ liệu (postgresql trong trường hợp này).

Có cách nào trong Rails chỉ thực thi mã SAU KHI một hành động cơ sở dữ liệu đã cam kết không? Về cơ bản, tôi muốn đảm bảo rằng khi Resque tải đối tượng, nó sẽ nhận được đối tượng mới nhất. Tôi biết điều này có thể đạt được bằng cách sử dụng móc after_commit trên mẫu Thing, nhưng tôi không muốn ở đó. Tôi chỉ cần điều này xảy ra trong bối cảnh cụ thể này, không phải mỗi lần mô hình có cam kết thay đổi thành DB.

Trả lời

5

Bạn cũng có thể thực hiện giao dịch. Cũng giống như ví dụ dưới đây:

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

Cập nhật: có một viên ngọc trong đó kêu gọi Sau giao dịch, với điều này bạn có thể giải quyết vấn đề của bạn. Đây là liên kết: http://xtargets.com/2012/03/08/understanding-and-solving-race-conditions-with-ruby-rails-and-background-workers/

+0

Vì vậy, chạy một giao dịch sẽ chặn thực thi 'ThingProcessor.queue_job (thing.id)' cho đến khi tất cả các truy vấn trong khối giao dịch đã cam kết? Tôi nghĩ rằng các khối giao dịch chỉ đảm bảo rằng nếu có sự thất bại trong bất kỳ hành động cơ sở dữ liệu nào trong khối, mọi thứ đều được sao lưu. Một tính năng cơ sở dữ liệu. Không phải là một tính năng ruby ​​ – Brian

+0

Tôi tin rằng nó là. Hãy thử và cho tôi biết nếu công trình. –

+0

Không thể tìm thấy bất kỳ tài liệu Rails nào nói rằng khối giao dịch chặn thực thi. Bất cứ ai biết dứt khoát? – Ben

0

Điều gì về việc đóng gói try xung quanh transaction để công việc được xếp hàng đợi chỉ khi thành công của giao dịch?

+0

Và không phải 'điều kiện' trả về đúng chỉ sau khi lưu thành công? ... Bạn cũng thử: 'ThingProcessor.queue_job (thing_id) nếu thing.save'. ? – user2669055

+2

Không, 'thing.save' trả về true sau khi Rails AR đã chuyển yêu cầu đến lớp DB. Đó là lý do tại sao callbacks after_save có thể có vấn đề điều kiện chủng tộc và tại sao có một cuộc gọi lại after_commit đợi cho đến sau khi DB đã viết và trả về một xác nhận. Vấn đề với việc thêm điều kiện hoặc thử khối là công việc sẽ không được xếp hàng đợi khi tình trạng cuộc đua bị tấn công. Nhiều mã sẽ phải được thêm vào vòng tròn và cố gắng xếp hàng công việc lại sau. Điều này làm tăng thêm sự phức tạp. – Ben

+0

Xin lỗi, tôi nghĩ một nửa nhận xét trước của tôi có thể sai. Có thể các khối giao dịch * thực hiện * block, và vì các cuộc gọi "lưu" Rails được gói trong khối giao dịch, cuộc gọi lưu có thể không thực sự trả về "true" cho đến khi giao dịch cơ sở dữ liệu hoàn tất thành công. Vì vậy, nó sẽ có thể quấn trong một điều kiện. Nhưng không làm giảm bớt vấn đề phải tìm cách để xếp hàng lại công việc. – Ben

Các vấn đề liên quan