diff --git a/app/services/project_identifiers/convert_project_to_semantic_service.rb b/app/services/project_identifiers/convert_project_to_semantic_service.rb index 788f120665f9..f02612b6d4d6 100644 --- a/app/services/project_identifiers/convert_project_to_semantic_service.rb +++ b/app/services/project_identifiers/convert_project_to_semantic_service.rb @@ -41,12 +41,10 @@ def initialize(project) end def call - ApplicationRecord.transaction do - fix_identifier_if_needed - reset_stale_identifiers - backfill_missing_ids - seed_alias_table - end + fix_identifier_if_needed + ApplicationRecord.transaction { reset_stale_identifiers } + ApplicationRecord.transaction { backfill_missing_ids } + ApplicationRecord.transaction { seed_alias_table } end private @@ -57,15 +55,8 @@ def fix_identifier_if_needed # Pure format check — no DB queries. return if ProjectIdentifiers::IdentifierAutofix::ProblematicIdentifiers.valid_format?(project.identifier) - # Serialize all concurrent identifier assignments with a transaction-level - # advisory lock. The lock is automatically released when the outer - # ApplicationRecord.transaction commits, so the next job waiting on it - # always reads a fully up-to-date exclusion set and can never generate a - # duplicate. Without this, parallel jobs can read the same exclusion set - # before any of them commits, then all pick the same candidate. - OpenProject::Mutex.with_advisory_lock( - Project, "semantic_identifier_generation", transaction: true - ) do + # Identifier assignments must run one at a time to avoid conflicts with concurrent renames or conversions. + OpenProject::Mutex.with_advisory_lock(Project, "semantic_identifier_generation") do assign_semantic_identifier end end