Reputation: 305
I am having an hard time figuring out the reason why my model is behaving like this.
It has 2 actions, cancel and feature, when attribute was already canceled it shouldn't be available to be featured.
To ensure that a feature is not performed on a canceled attribute I am using Redis-Mutex to lock the attribute for exclusivity and make the proper verifications inside the block. This should ensure that records being worked out on one side are not simultaneously being worked on the other:
Feature Action:
mutex = RedisMutex.new(record_to_feature, block: 30, sleep: 0.1)
if mutex.lock
record_to_feature = record_to_feature.reload
if record_to_feature.reload.active?
record_to_feature.reload.update_attributes(featured: true)
end
mutex.unlock
end
Cancel Action:
mutex = RedisMutex.new(record_to_cancel, block: 30, sleep: 0.1)
if mutex.lock
record_to_cancel = record_to_cancel.reload
if !record_to_cancel.reload.featured?
record_to_cancel.reload.update_attributes(active: false)
end
mutex.unlock
end
I want to understand how is it possible that sometimes (rare) the attribute is first canceled and then featured - the other way: feature and then cancel may also occur just I haven't detected it.
Please let me know if this is a bad approach and if yes what would be a good way to fix this issue.
Upvotes: 3
Views: 305
Reputation: 305
OK, I think I finally found the problem.
On redix-mutes, I should use the non-blocking mode to avoid these kind of issues.
This way if the lock couldn't be made in first instance, the process will be aborted and no problems are created.
The way I had id (block 30 seconds) it was trying for 30 seconds (polling intervals of 100ms) to establish a lock and somehow that could cause multiple issues like these. Example: two threads trying to get the lock on a record and the last one establishes it first.
The lock method returns true when the lock has been successfully acquired, or returns false when the attempts failed after the seconds specified with :block. When 0 is given to :block, it is set to non-blocking mode and immediately returns false.
https://github.com/kenn/redis-mutex
Upvotes: 0
Reputation: 8467
When active == true and featured == false, you can invoke the feature action. This will set featured to true since active == true.
Then you can call the cancel action. Since featured == true, action is set to false.
Now, active == false and featured == true.
I drew up a simple state diagram to show all the possible changes of state. I did not include no-op state changes, for example, you can call the feature or cancel actions when both active and featured are false, but this won't do anything.
The arrows all represent paths where update_attributes is called. The circles represent the different states that the record can be found in.
There may not be a state where both active and featured are both false -- I don't see how the record is constructed so I include this for completeness.
It is easy to see how the record can be featured, yet, not active.
EDIT:
Here is an updated state diagram with your if statement fixed, and simplified:
So it seems not possible to cancel, then feature.
One thing that occurs to me is that you are not checking the return value from update_attributes to see if the update was successful. Could a failed update explain the observed behavior?
Upvotes: 5