From 2acbe6c726df070957220474bd29fe8605a6e0f9 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Thu, 4 Sep 2025 18:56:16 +0100 Subject: [PATCH] Lock changesets during a diff upload --- .../api/changesets/uploads_controller.rb | 8 ++--- .../api/changesets/uploads_controller_test.rb | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/changesets/uploads_controller.rb b/app/controllers/api/changesets/uploads_controller.rb index d9d16e25f..a6f687b00 100644 --- a/app/controllers/api/changesets/uploads_controller.rb +++ b/app/controllers/api/changesets/uploads_controller.rb @@ -28,11 +28,11 @@ module Api # Returns: a diffResult document, as described in # http://wiki.openstreetmap.org/wiki/OSM_Protocol_Version_0.6 def create - changeset = Changeset.find(params[:changeset_id]) - check_changeset_consistency(changeset, current_user) - - diff_reader = DiffReader.new(request.raw_post, changeset) Changeset.transaction do + changeset = Changeset.lock.find(params[:changeset_id]) + check_changeset_consistency(changeset, current_user) + + diff_reader = DiffReader.new(request.raw_post, changeset) result = diff_reader.commit # the number of changes in this changeset has already been # updated and is visible in this transaction so we don't need diff --git a/test/controllers/api/changesets/uploads_controller_test.rb b/test/controllers/api/changesets/uploads_controller_test.rb index 3c2a2c1f3..efa3ec224 100644 --- a/test/controllers/api/changesets/uploads_controller_test.rb +++ b/test/controllers/api/changesets/uploads_controller_test.rb @@ -198,6 +198,35 @@ module Api assert_dom "osmError>message", 1 end + def test_upload_race_condition + user = create(:user) + changeset = create(:changeset, :user => user) + + diff = <<~CHANGESET + + + + + + CHANGESET + + auth_header = bearer_authorization_header user, :scopes => %w[write_api] + path = api_changeset_upload_path(changeset) + concurrency_level = 16 + + threads = Array.new(concurrency_level) do + Thread.new do + post path, :params => diff, :headers => auth_header + end + end + threads.each(&:join) + + changeset.reload + assert_equal concurrency_level, changeset.num_changes + assert_predicate changeset, :num_type_changes_in_sync? + assert_equal concurrency_level, changeset.num_created_nodes + end + # ------------------------------------- # Test creating elements. # ------------------------------------- -- 2.39.5