简体   繁体   中英

How to call controller action from _Sidekiq Worker in Rails 5.2.4?

My RoR application triggers data treatment scripts (SAS technology) from Linux operating system. An execute method in the Scheduler::ProductionExecutionsController drives the interactions with the scripts.

Scheduler::ProductionExecutionsController

  # POST /production_executions/1/execute
  def execute
    @production_execution = ProductionExecution.find(params[:id])
    @production_execution.update_attributes(started_at: Time.now, 
                                            status_id: statuses.find { |x| x["code"] == "RUNNING" }.id)

--- Scripts interaction ---

  @production_execution.update_attributes(ended_at: Time.now,
                                          status_id: statuses.find { |x| x["code"] == "FINISHED" }.id,
                                          source_records_count: global_input_count,
                                          processed_count: global_output_count,
                                          error_message: nil
                                          )

  respond_to do |format|
    format.html { redirect_back fallback_location: @production_execution, notice: @msg }
    format.js
  end

This method is called using this syntax: link_to "Go!", execute_scheduler_production_execution_path(execution) . The route is defined and it works as expected.

As some scripts can take over a minute to execute, I need to execute the scripts in dedicated jobs, and sometimes schedule them. So I installed Sidekiq and defined a Scheduler::ScriptWorker :

class Scheduler::ScriptWorker
  include Sidekiq::Worker
  sidekiq_options queue: :default, tags: ['script']

  def perform(execution_id)
    puts "Sidekiq job start"
    puts execution_id
    redirect_to execute_scheduler_production_execution_path(execution_id) and return
  end

end

The executions are queued with Scheduler::ScriptWorker.perform_async(@execution.id)

Sidekiq operates correctly, but each time the worker is invoked, it raise the following error:

WARN: NoMethodError: undefined method `execute_scheduler_production_execution_path' for #<Scheduler::ScriptWorker:0x000000000b15ded8>

Is it the right way to do this, and how can I solve this issue? Thanks for you help!

The short answer is: you can't

You can't perform a redirect from an asynchronous Sidekiq worker. When the user clicks "Go!", an HTTP request is made to your Rails web application. Rails hands the request off to a controller action, which in your case spawns an async sidekiq job. The controller action logic continues and Rails completes the HTTP request with an HTTP response, even if your sidekiq worker is still running. You cannot broadcast a message from a sidekiq worker back to the user who initiated the task

I'm sorry this does not resolve your issue. You'll need to take a different approach.

As stated by Sean Huber, converting a front-end job into a backend job requires a touch of architecture. Through refactoring, the experimental execute method was moved to a helper and renamed execute_ssh . A new run_once method in the controller fires the helper's execute_ssh method and reloads the page.

script_worker.rb

class Scheduler::ScriptWorker
  include Sidekiq::Worker
  include SchedulerExecutionHelper
  include ParametersHelper
  sidekiq_options queue: :default, tags: ['script'], retry: false

  def perform(execution_id)
    puts "Sidekiq job start"
    puts execution_id
    execute_ssh execution_id
  end
end

scheduler_execution_helper.rb

  def execute_ssh(execution_id)
    puts "--- Switched to helper"
    @production_execution = ProductionExecution.find(execution_id)
    puts @production_execution.id

    @production_execution.update_attributes(started_at: Time.now, status_id: statuses.find { |x| x["code"] == "RUNNING" }.id)

--- Scripts interaction ---

  @production_execution.update_attributes(ended_at: Time.now,
                                          status_id: statuses.find { |x| x["code"] == "FINISHED" }.id,
                                          source_records_count: global_input_count,
                                          processed_count: global_output_count,
                                          error_message: nil
                                          )

  end

production_schedules_controller

  def run_once
    @job = @production_schedule.parent
    @execution = @job.production_executions.build(playground_id: @job.playground_id,
                                                  production_job_id: @job.id,
                                                  environment_id: @production_schedule.environment_id,
                                                  owner_id: current_user.id,
                                                  status_id: options_for('Statuses', 'Scheduler').find { |x| x["code"] == "READY" }.id || 0)
    if @execution.save
      @job.production_events.where(production_execution_id: nil).each do |event|
        execution_event = event.dup
        execution_event.production_execution_id = @execution.id
        execution_event.return_value = 0
        execution_event.status_id = statuses.find { |x| x["code"] == "READY" }.id
        execution_event.save
      end
      Scheduler::ScriptWorker.perform_async(@execution.id)
      redirect_to scheduler_production_job_path(@job)
    else
    end
  end

This way, the controller remains thin, the worker can be easily reused, and the logic is in the helper module.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM