Skip to content

Commit 38855fd

Browse files
authored
Prevent 500 on parallel object creation (#3918)
* Prevent 500 on parallel object creation While fixing #3899, we found further possible occurrences of 500 errors in severel _create actions when doing parallel requests. In this PR we want to address them by adding an around_save to the corresponding model in which we catch Sequel::UniqueConstraintViolation errors for the case when ne POST bypasses the other one and inserts the record into the db after the first POST has passed the validate function but before the actual insert.
1 parent 5d29711 commit 38855fd

29 files changed

+405
-11
lines changed

app/actions/service_credential_binding_app_create.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ def precursor(service_instance, message:, app: nil, volume_mount_services_enable
4040
MetadataUpdate.update(b, message)
4141
end
4242
end
43-
rescue Sequel::ValidationFailed, Sequel::UniqueConstraintViolation => e
43+
rescue Sequel::ValidationFailed => e
44+
if e.message.include?('app_guid and name unique')
45+
raise UnprocessableCreate.new("The binding name is invalid. App binding names must be unique. The app already has a binding with name '#{message.name}'.")
46+
elsif e.message.include?('service_instance_guid and app_guid unique')
47+
raise UnprocessableCreate.new('The app is already bound to the service instance.')
48+
end
49+
4450
raise UnprocessableCreate.new(e.full_message)
4551
end
4652

app/actions/space_create.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def create(org, message)
2525
attr_reader :user_audit_info
2626

2727
def validation_error!(error)
28-
error!('Name must be unique per organization') if error.is_a?(Space::DBNameUniqueRaceError) || error.errors.on(%i[organization_id name])&.include?(:unique)
28+
error!('Name must be unique per organization') if error.errors.on(%i[organization_id name])&.include?(:unique)
2929
error!(error.message)
3030
end
3131

app/actions/space_update.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def update(space, message)
2323
end
2424

2525
def validation_error!(error, space)
26-
error!("Organization '#{space.organization.name}' already contains a space with name '#{space.name}'.") if error.is_a?(Space::DBNameUniqueRaceError) || error.errors.on(%i[
26+
error!("Organization '#{space.organization.name}' already contains a space with name '#{space.name}'.") if error.errors.on(%i[
2727
organization_id name
2828
])&.include?(:unique)
2929
error!(error.message)

app/models/runtime/app_model.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ def after_initialize
7676
self.enable_ssh = Config.config.get(:default_app_ssh_access) if enable_ssh.nil?
7777
end
7878

79+
def around_save
80+
yield
81+
rescue Sequel::UniqueConstraintViolation => e
82+
raise e unless e.message.include?('apps_v3_space_guid_name_index')
83+
84+
errors.add(%i[space_guid name], :unique)
85+
raise validation_failed_error
86+
end
87+
7988
def validate
8089
super
8190
validates_presence :name

app/models/runtime/domain.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ def shared_or_owned_by(organization_ids)
8585
add_association_dependencies labels: :destroy
8686
add_association_dependencies annotations: :destroy
8787

88+
def around_save
89+
yield
90+
rescue Sequel::UniqueConstraintViolation => e
91+
raise e unless e.message.include?('domains_name_index')
92+
93+
errors.add(:name, :unique)
94+
raise validation_failed_error
95+
end
96+
8897
def validate
8998
validates_presence :name
9099
validates_unique :name, dataset: Domain.dataset

app/models/runtime/helpers/organization_role_mixin.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ def before_create
1313
self.guid ||= SecureRandom.uuid
1414
end
1515

16+
def around_save
17+
yield
18+
rescue Sequel::UniqueConstraintViolation => e
19+
unique_indexes = %w[org_users_idx org_auditors_idx org_managers_idx org_billing_managers_idx]
20+
raise e unless unique_indexes.any? { |pattern| e.message.include?(pattern) }
21+
22+
errors.add(%i[organization_id user_id], :unique)
23+
raise validation_failed_error
24+
end
25+
1626
def validate
1727
validates_unique %i[organization_id user_id]
1828
validates_presence :organization_id

app/models/runtime/helpers/space_role_mixin.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ def before_create
1313
self.guid ||= SecureRandom.uuid
1414
end
1515

16+
def around_save
17+
yield
18+
rescue Sequel::UniqueConstraintViolation => e
19+
unique_indexes = %w[space_developers_idx space_auditors_idx space_managers_idx spaces_supporters_user_space_index]
20+
raise e unless unique_indexes.any? { |pattern| e.message.include?(pattern) }
21+
22+
errors.add(%i[space_id user_id], :unique)
23+
raise validation_failed_error
24+
end
25+
1626
def validate
1727
validates_presence :space_id
1828
validates_presence :user_id

app/models/runtime/isolation_segment_model.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ class IsolationSegmentModel < Sequel::Model(:isolation_segments)
1919
add_association_dependencies labels: :destroy
2020
add_association_dependencies annotations: :destroy
2121

22+
def around_save
23+
yield
24+
rescue Sequel::UniqueConstraintViolation => e
25+
raise e unless e.message.include?('isolation_segment_name_unique_constraint')
26+
27+
errors.add(:name, :unique)
28+
raise validation_failed_error
29+
end
30+
2231
def validate
2332
validates_format ISOLATION_SEGMENT_MODEL_REGEX, :name, message: Sequel.lit('Isolation Segment names can only contain non-blank unicode characters')
2433

app/models/runtime/organization.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ def before_save
207207
super
208208
end
209209

210+
def around_save
211+
yield
212+
rescue Sequel::UniqueConstraintViolation => e
213+
raise e unless e.message.include?('organizations_name_index')
214+
215+
errors.add(:name, :unique)
216+
raise validation_failed_error
217+
end
218+
210219
def validate
211220
validates_presence :name
212221
validates_unique :name

app/models/runtime/space.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class InvalidSupporterRelation < CloudController::Errors::InvalidRelation; end
99
class InvalidManagerRelation < CloudController::Errors::InvalidRelation; end
1010
class InvalidSpaceQuotaRelation < CloudController::Errors::InvalidRelation; end
1111
class UnauthorizedAccessToPrivateDomain < RuntimeError; end
12+
# needed for v2 spaces_controller
1213
class DBNameUniqueRaceError < Sequel::ValidationFailed; end
1314

1415
SPACE_NAME_REGEX = /\A[[:alnum:][:punct:][:print:]]+\Z/
@@ -218,9 +219,10 @@ def in_organization?(user)
218219
def around_save
219220
yield
220221
rescue Sequel::UniqueConstraintViolation => e
221-
raise DBNameUniqueRaceError.new(e) if e.message.try(:include?, 'spaces_org_id_name_index')
222+
raise e unless e.message.include?('spaces_org_id_name_index')
222223

223-
raise
224+
errors.add(%i[organization_id name], :unique)
225+
raise validation_failed_error
224226
end
225227

226228
def validate

0 commit comments

Comments
 (0)