From ad7b47293d9e1c9e941a722cc366e447f9f07f11 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 2 Aug 2026 20:13:22 +0200 Subject: [PATCH] fix: ensured namespace creation is working concurrently --- app/models/concerns/namespace_parent.rb | 9 +++---- spec/models/concerns/namespace_parent_spec.rb | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 spec/models/concerns/namespace_parent_spec.rb diff --git a/app/models/concerns/namespace_parent.rb b/app/models/concerns/namespace_parent.rb index 1877c757..224627df 100644 --- a/app/models/concerns/namespace_parent.rb +++ b/app/models/concerns/namespace_parent.rb @@ -9,12 +9,11 @@ module NamespaceParent def ensure_namespace return namespace if namespace.present? + return build_namespace unless persisted? - ns = build_namespace - if persisted? - ns.save - ns.ensure_personal_namespace_administrator! if ns.user_type? - end + ns = Namespace.create_or_find_by(parent: self) + association(:namespace).target = ns + ns.ensure_personal_namespace_administrator! if ns.user_type? ns end end diff --git a/spec/models/concerns/namespace_parent_spec.rb b/spec/models/concerns/namespace_parent_spec.rb new file mode 100644 index 00000000..7ed5f63d --- /dev/null +++ b/spec/models/concerns/namespace_parent_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe NamespaceParent do + describe '#ensure_namespace', :disable_transaction do + let!(:user) { create(:user) } + let(:thread_count) { [ActiveRecord::Base.connection_pool.size - 1, 2].max } + + it 'creates exactly one namespace when called concurrently from separate connections' do + barrier = Concurrent::CyclicBarrier.new(thread_count) + + results = Array.new(thread_count) do + Thread.new do + ActiveRecord::Base.connection_pool.with_connection do + barrier.wait + User.find(user.id).ensure_namespace + end + end + end.map(&:value) + + expect(results).to all(be_a(Namespace)) + expect(results.map(&:id).uniq.size).to eq(1) + expect(Namespace.where(parent: user).count).to eq(1) + end + end +end