Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport 'Fix memory leak with user answers serializer (at survey export)' to v0.27 #11241

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 6 additions & 6 deletions decidim-forms/lib/decidim/forms/user_answers_serializer.rb
Expand Up @@ -39,19 +39,19 @@ def hash_for(answer)
end

def questions_hash
return {} if questionnaire&.questions.blank?
questionnaire_id = @answers.first&.decidim_questionnaire_id
return {} unless questionnaire_id

questionnaire.questions.each.inject({}) do |serialized, question|
questions = Decidim::Forms::Question.where(decidim_questionnaire_id: questionnaire_id)
return {} if questions.none?

questions.each.inject({}) do |serialized, question|
serialized.update(
translated_question_key(question.position, question.body) => ""
)
end
end

def questionnaire
@answers.first&.questionnaire
end

def translated_question_key(idx, body)
"#{idx + 1}. #{translated_attribute(body)}"
end
Expand Down
Expand Up @@ -146,6 +146,48 @@ module Forms
expect(serialized).to include("5. #{translated(conditional_question.body, locale: I18n.locale)}" => "")
end
end

context "when the questionnaire body is very long" do
let!(:questionnaire) { create(:questionnaire, questionnaire_for: questionable, description: questionnaire_description) }
let(:questionnaire_description) do
Decidim::Faker::Localized.wrapped("<p>", "</p>") do
Decidim::Faker::Localized.localized { "a" * 1_000_000 }
end
end
let!(:users) { create_list(:user, 100, organization: questionable.organization) }

before do
users.each do |user|
questions.each do |question|
create(:answer, questionnaire: questionnaire, question: question, user: user)
end
end
end

it "does not load the questionnaire description to memory every time when iterating an answer" do
# NOTE:
# For this test it is important to fetch the single user "answer
# sets" to an array and store them there because this is the same
# way the answers are loaded e.g. in the survey component export
# functionality. The export had previously a memory leak because the
# questionnaire is fetched individually for each "answer set" and if
# it has a very long description, it caused the description to be
# stored multiple times within the array (for each "answer set"
# separately) causing a out of memory errors when there is a large
# amount of answers.
all_answers = Decidim::Forms::QuestionnaireUserAnswers.for(questionnaire)

initial_memory = memory_usage
all_answers.each do |answer_set|
described_class.new(answer_set).serialize
end
expect(memory_usage - initial_memory).to be < 10_000
end

def memory_usage
`ps -o rss #{Process.pid}`.lines.last.to_i
end
end
end
end
end
Expand Down