From 133f00bedd990e4220cbd89cbfbe7f69e98c0c76 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 21 Apr 2017 05:55:24 +0000 Subject: Move records to the ghost user in a transaction. - While deleting a user, some of the user's associated records are moved to the ghost user so they aren't deleted. The user is blocked before these records are moved, to prevent the user from creating new records while the migration is happening, and so preventing a data race. - Previously, if the migration failed, the user would _remain_ blocked, which is not the expected behavior. On the other hand, we can't just stick the block + migration into a transaction, because we want the block to be committed before the migration starts (for the data race reason mentioned above). - One solution (implemented in this commit) is to block the user in a parent transaction, migrate the associated records in a nested sub-transaction, and then unblock the user in the parent transaction if the sub-transaction fails. --- .../users/migrate_to_ghost_user_service.rb | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) (limited to 'app/services/users/migrate_to_ghost_user_service.rb') diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb index 1e1ed1791ec..4628c4c6f6e 100644 --- a/app/services/users/migrate_to_ghost_user_service.rb +++ b/app/services/users/migrate_to_ghost_user_service.rb @@ -15,27 +15,39 @@ module Users end def execute - # Block the user before moving records to prevent a data race. - # For example, if the user creates an issue after `migrate_issues` - # runs and before the user is destroyed, the destroy will fail with - # an exception. - user.block + transition = user.block_transition user.transaction do + # Block the user before moving records to prevent a data race. + # For example, if the user creates an issue after `migrate_issues` + # runs and before the user is destroyed, the destroy will fail with + # an exception. + user.block + + # Reverse the user block if record migration fails + if !migrate_records && transition + transition.rollback + user.save! + end + end + + user.reload + end + + private + + def migrate_records + user.transaction(requires_new: true) do @ghost_user = User.ghost migrate_issues migrate_merge_requests migrate_notes migrate_abuse_reports - migrate_award_emoji + migrate_award_emojis end - - user.reload end - private - def migrate_issues user.issues.update_all(author_id: ghost_user.id) end @@ -52,7 +64,7 @@ module Users user.reported_abuse_reports.update_all(reporter_id: ghost_user.id) end - def migrate_award_emoji + def migrate_award_emojis user.award_emoji.update_all(user_id: ghost_user.id) end end -- cgit v1.2.3