Merge branch 'fix_thread_safety_bug' into 0-10-stable

This commit is contained in:
Benjamin Fleischer 2018-11-01 16:17:02 -05:00
commit 09264da273
3 changed files with 57 additions and 2 deletions

View File

@ -347,7 +347,7 @@ module ActiveModel
return Enumerator.new {} unless object
Enumerator.new do |y|
self.class._reflections.each do |key, reflection|
(self.instance_reflections ||= self.class._reflections.deep_dup).each do |key, reflection|
next if reflection.excluded?(self)
next unless include_directive.key?(key)
@ -411,6 +411,6 @@ module ActiveModel
protected
attr_accessor :instance_options
attr_accessor :instance_options, :instance_reflections
end
end

View File

@ -151,6 +151,9 @@ module ActiveModel
# @yield [ActiveModel::Serializer]
# @return [:nil, associated resource or resource collection]
def value(serializer, include_slice)
# NOTE(BF): This method isn't thread-safe because the _reflections class attribute is not thread-safe
# Therefore, when we build associations from reflections, we dup the entire reflection instance.
# Better solutions much appreciated!
@object = serializer.object
@scope = serializer.scope

View File

@ -423,5 +423,57 @@ module ActiveModel
end
# rubocop:enable Metrics/AbcSize
end
class ThreadedReflectionTest < ActiveSupport::TestCase
class Post < ::Model
attributes :id, :title, :body
associations :comments
end
class Comment < ::Model
attributes :id, :body
associations :post
end
class CommentSerializer < ActiveModel::Serializer
type 'comment'
attributes :id, :body
has_one :post
end
class PostSerializer < ActiveModel::Serializer
type 'post'
attributes :id, :title, :body
has_many :comments, serializer: CommentSerializer do
sleep 0.1
object.comments
end
end
# per https://github.com/rails-api/active_model_serializers/issues/2270
def test_concurrent_serialization
post1 = Post.new(id: 1, title: 'Post 1 Title', body: 'Post 1 Body')
post1.comments = [Comment.new(id: 1, body: 'Comment on Post 1', post: post1)]
post2 = Post.new(id: 2, title: 'Post 2 Title', body: 'Post 2 Body')
post2.comments = [Comment.new(id: 2, body: 'Comment on Post 2', post: post2)]
serialized_posts = {
first: Set.new,
second: Set.new
}
t1 = Thread.new do
10.times do
serialized_posts[:first] << PostSerializer.new(post1, {}).to_json
end
end
t2 = Thread.new do
10.times do
serialized_posts[:second] << PostSerializer.new(post2, {}).to_json
end
end
t1.join
t2.join
expected_first_post_serialization = '{"id":1,"title":"Post 1 Title","body":"Post 1 Body","comments":[{"id":1,"body":"Comment on Post 1"}]}'
expected_second_post_serialization = '{"id":2,"title":"Post 2 Title","body":"Post 2 Body","comments":[{"id":2,"body":"Comment on Post 2"}]}'
assert_equal [expected_second_post_serialization], serialized_posts[:second].to_a
assert_equal [expected_first_post_serialization], serialized_posts[:first].to_a
end
end
end
end