Preventing Race Parenthood in Rails

I have the following models:

class Lyric < ActiveRecord::Base belongs_to :user belongs_to :song after_create :add_to_song end class Song < ActiveRecord::Base belongs_to :user has_many :lyrics end 

The idea is that the user can add any number of songs for the song. If you enter lyrics for a new song that does not yet exist for this user, a new song is created for this user. This is achieved by calling the after_create 'add_to_song' method, which checks if the user has lyrics from this song:

 def add_to_song sl = self.song_line # Check for adjacent songs prior_song = Song.where(:user_id => self.user.id, :title=> sl.title, :artist => sl.artist, :last_line => sl.linenum-1).first next_song = Song.where(:user_id => self.user.id, :title=> sl.title, :artist => sl.artist, :frst_line => sl.linenum+1).first # Case 1 - No existing song if !prior_song && !next_song song = Song.create!(:user_id => self.user.id, :length => 1, :title=> sl.title, :artist => sl.artist, :frst_line => sl.linenum, :last_line => sl.linenum ) self.update_attribute( :song_id, song.id ) # Case 2 - Lyric is between two songs -> merge songs elsif prior_song && next_song prior_song.absorb( next_song, self ) # Case 3 - Lyric is new first lyric of existing song elsif next_song next_song.expand( self ) # Case 4 - Lyric is new last lyric of existing song else prior_song.expand( self ) end end 

The add_to_song method also combines two "songs" into one if the user adds a Lyric link. In other words, if the user has the first and third lines of a song, they are considered two different songs until she adds the second line of the same song.

Problem

When a user adds several texts from the same song at the same time (choosing several of them from the search results), the race condition is sometimes found in MySQL, and two song compositions are created for the same song, although the lyrics adjacent to each other should be combined in one "Song". (The unfortunate result of this is that the lyrics are presented in the correct order.)

I read endless posts about optimistic and pessimistic blocking, etc., and tried various options, but it seemed I could not get rid of this problem. It seems that locking the entire Song table too much every time the user creates lyrics.

Is this the only way to prevent this? (This seems to be a huge success for performance). Do I have something fundamentally wrong in my circuit? I would suggest that this is a common problem in many projects, but it seems to arise too often, as far as I can tell. It seems that whenever a parent association is created in the after_create method, there is a chance of a race condition if the creation of the parent model (in this case Song) depends on the existence of another child (in this case, Lyric).

+4
source share
1 answer

If you don't want to lock the table, there is an ugly way to prevent this: mutexes.

Something like that:

 File.open(MUTEX_FILE_PATH, "w") unless File.exists?(MUTEX_FILE_PATH) mutex = File.new(MUTEX_FILE_PATH,"r+") begin mutex.flock(File::LOCK_EX) ...code... ensure mutex.flock(File::LOCK_UN) end 

It works without locking the entire table. You can do even better for performance and create a mutex using a user ID, this way the block will work for each user, not for anyone.

I really didn’t receive the check for the next song and did not check the previous song, but if you have the user_many personal lyrics, the songs do not work better than your current user has_many songs through the lyrics?

0
source

All Articles