A shared work queue

What is a good way to set up a queue for users to share so that two users don’t get the same item from the queue?

I have a shared queue for multiple users. Each worker clicks the link to start working which brings up the next available item to be worked. They either mark it complete, in which case it is flagged as such and not included in the scope that is still workable.

If the task is flagged as incomplete it’s last worked date is set as the current time. The workable scope is sorted by last worked date so this effectively sends it to the back of the list.

I’m trying to figure out the best way to guarantee that no two users get the same task. In other words, when someone takes a task from the queue it is no longer available to the others.

The obvious thought is to update their last worked date immedialtely when someone takes one, but what if two people simultaneously take the same one?

I’m sure this problem has been solved before. Any ideas?

This is indeed a common problem. Essentially, you want to prevent a user from updating a record when its representation of that record was stale (that is, someone else changed it between the time the user saw it and the time they decided to do something with it).

Rails has some built in support for this with both optimistic and pessimistic locking schemes. Check those out and see if they will help.

I really want to catch it sooner than that. I don’t want two different users to be able to draw it from the queue. If one user gets it, no one else should be able to get it. I don’t think locking is what I’m looking for, but I could be wrong.

The trouble is that on the web, you’re only able to keep that list of tasks up to date either via polling or with web sockets, which would push the fact that a task has been taking to all clients rather than waiting for the client to poll for an updated list. In either event, you’d still need some flavor of locking to prevent race conditions.

After doing some reading on locking I think I have a solution. That is if I understand it correctly.

If I use something like this…

Task.transaction do
  task = Task.order('last_worked_date ASC').limit(1).lock(true).first
  task.update_attribute(:last_worked_date, Time.now)

Rails will use Postgresql’s FOR UPDATE clause for locking.

When the second user tries to take a task with the same query, it will wait to read the record after the lock has released at which time the date will no longer be the oldest worked, so it will get the next one on the list.

Does that sound right?

The first link below is where I found supporting information and the second is where I verified it:

good description
verified (I think)