Jason
Jason

Reputation: 1129

Prevent race condition when generating unique code

The following code returns a unique 3 character code by continually checking if the genereated code already exists in the db. Once it finds one that does not exist the loop exits.

How can I protect against race conditions which could lead to non-unique codes being returned?

               pubcode = Pubcode.find_by_pub_id(current_pub.id)

               new_id = nil  
               begin              
                  new_id = SecureRandom.hex(2)[0..2].to_s 
                  old_id = Pubcode.find_by_guid(new_id) 
                  if !old_id.nil? 
                   pubcode.guid = new_id
                   pubcode.save
                  end
                end while (old_id)

Upvotes: 1

Views: 529

Answers (3)

mcfinnigan
mcfinnigan

Reputation: 11638

How can I protect against race conditions which could lead to non-unique codes being returned?

Don't use the database as a synchronization point. Apart from synchronization issues, your code is susceptible to slowdown as the number of available codes shrinks. There is no guarantee your loop would terminate.

A far better approach to this would be to have a service which pre-generates a batch of unique identifiers and hands these out on a first-come, first-served basis.

Given that you are only using 3 characters for this code, you can only store ~= 17 000 records - you could generate the entire list of permutations of three character codes up front, and remove entries from this list as you allocate them.

Upvotes: 4

sawa
sawa

Reputation: 168081

Preventing a race is done by putting the process in a mutex:

@mutex = Mutex.new

within the method that calls your code:

@mutex.synchronize do
  # Whatever process you want to avoid race
end

But a problem with your approach is that your loop may never end since you are using randomness.

Upvotes: 0

spickermann
spickermann

Reputation: 106802

You can add a unique index on the database column, and then just try to update a Pubcode with a random uuid. If that fails because of the unique index, just try another code:

pubcode = Pubcode.find_by_pub_id!(current_pub.id)

begin
  pupcode.update!(guid: SecureRandom.hex(2)[0..2])
rescue ActiveRecord::StatementInvalid => e
  retry 
end

Perhaps you want to count the number of retries and raise the exception if there was no code found within a certain number of tries (because there are only 4096 possible ids).

Upvotes: 1

Related Questions