diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b08465a54b..9724d70967 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -695,6 +695,7 @@ Rails/ApplicationJob: - 'app/jobs/report_job.rb' - 'app/jobs/subscription_confirm_job.rb' - 'app/jobs/subscription_placement_job.rb' + - 'spec/services/job_processor_spec.rb' # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 2dc18e4092..fe926a3441 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -55,10 +55,11 @@ module Admin def render_report_as(format) if OpenFoodNetwork::FeatureToggle.enabled?(:background_reports, spree_current_user) - job = ReportJob.perform_later( + job = ReportJob.new + JobProcessor.perform_forked( + job, report_class, spree_current_user, params, format ) - sleep 1 until job.done? # This result has been rendered by Rails in safe mode already. job.result.html_safe # rubocop:disable Rails/OutputSafety diff --git a/app/services/job_processor.rb b/app/services/job_processor.rb new file mode 100644 index 0000000000..8eed908b97 --- /dev/null +++ b/app/services/job_processor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Forks into a separate process to contain memory usage and timeout errors. +class JobProcessor + def self.perform_forked(job, *args) + fork do + Process.setproctitle("Job worker #{job.job_id}") + job.perform(*args) + + # Exit is not a good idea within a Rails process but Rubocop doesn't know + # that we are in a forked process. + exit # rubocop:disable Rails/Exit + end + + Process.waitall + end +end diff --git a/spec/services/job_processor_spec.rb b/spec/services/job_processor_spec.rb new file mode 100644 index 0000000000..0044038ffe --- /dev/null +++ b/spec/services/job_processor_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +class TestJob < ActiveJob::Base + def initialize + @file = Tempfile.new("test-job-result") + super + end + + def perform(message) + @file.write(message) + end + + def result + @file.rewind + @file.read + end +end + +describe JobProcessor do + describe ".perform_forked" do + let(:job) { TestJob.new } + + it "executes a job" do + JobProcessor.perform_forked(job, "hello") + + expect(job.result).to eq "hello" + end + end +end