-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix raising validation if followable already exist(race condition) #5826
Fix raising validation if followable already exist(race condition) #5826
Conversation
begin | ||
user_follow = current_user.follow(followable) | ||
rescue ActiveRecord::RecordInvalid => e | ||
Rails.logger.error(e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to do more than just log an error here, e.g. update a DataDog metric. @mstruve can point you in the right direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm torn. I don't really think it is that important but at the same time, I have learned when in doubt log it so let's do DataDogStatsClient.increment("users.invalid_follow")
instead of Rails.logger here to track it on the off chance we want the metric in the future for some reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh, I wasn't sure either, hence the "we may want to do more". But like you I lean in the direction of logging more.
@@ -68,7 +68,11 @@ def follow_params | |||
end | |||
|
|||
def follow(followable, need_notification: false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def
is an implicit begin
, so this could be:
def follow(followable, need_notification: false)
user_follow = current_user.follow(followable)
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error(e)
end
See e.g. https://www.rubyguides.com/2019/06/ruby-rescue-exceptions/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @citizen428 . I am seeing that codeclimate/diff-coverage check failed. How can I do to turn this check in green?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately the report is not very forthcoming on details, so I think you can ignore it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@citizen428 If I don't use begin
then I have to use return
in ensure
block. Is this ok for you?
def follow(followable, need_notification: false)
user_follow = current_user.follow(followable)
rescue ActiveRecord::RecordInvalid => e
DataDogStatsClient.increment("users.invalid_follow")
ensure
Notification.send_new_follower_notification(user_follow) if need_notification
return "followed"
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would set it up like this and actually I think that reads pretty cleanly personally. We will see what @citizen428 thinks.
def follow(followable, need_notification: false)
user_follow = current_user.follow(followable)
Notification.send_new_follower_notification(user_follow) if need_notification
rescue ActiveRecord::RecordInvalid => e
DataDogStatsClient.increment("users.invalid_follow")
ensure
return "followed"
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if we should really return "followed" in case something went wrong. Maybe just have different returns based on whether we succeed or fail?
def follow(followable, need_notification: false)
user_follow = current_user.follow(followable)
Notification.send_new_follower_notification(user_follow) if need_notification
"followed"
rescue ActiveRecord::RecordInvalid => e
DataDogStatsClient.increment("users.invalid_follow")
# some other message, e.g. "previously followed"
end
Either way, it's such a small detail I'll leave it to your judgement what you think is best, i.e. if you want to keep as-is, I have no problem with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you folks for your help. I ended up removing ensure
because Rubocop doesn't like to have a return
within ensure
.
user_follow = current_user.follow(followable) | ||
rescue ActiveRecord::RecordInvalid => e | ||
Rails.logger.error(e) | ||
end | ||
Notification.send_new_follower_notification(user_follow) if need_notification |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add this to our happy path/begin block since we know if the follow is invalid we dont need to send a notification.
… in follow method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🚀
I know I approved it but it would probably be a good idea to add a spec for the new failure mode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thank you @gonzar11!
I thought about that but I couldn't figure out how to simulate this race condition. Do you mean by making request from multiple threads o something like that? |
I was thinking by creating one and then attempting to use this endpoint to create a second immediately after. But I can live without it :) |
Oh one last thing, did you sign the CLA? |
I did it :) |
|
What type of PR is this? (check all applicable)
Description
It rescues from ActiveRecord::RecordInvalid assuming that it only be raised in case that we have a race condition trying to create the same Follow model from different threads/request.
Related Tickets & Documents
closes #5793
Mobile & Desktop Screenshots/Recordings (if there are UI changes)
Added to documentation?
[optional] What gif best describes this PR or how it makes you feel?
My first contribution to an open source project.