Zhang Jiayu
Zhang Jiayu

Reputation: 143

A singleton keeps changing its status per request in rails

This is really weird to me. Have been frustrated for hours. Once a RUN request received, RUNNER will start out a background thread running forever. At anytime, there will be only one running thread (i use mutex). The problem is, for status request, sometimes it returns true, sometimes it returns false.

(done method never been called, since the backend thread will run forever, so there is no way is_running? could be false)

#config/initializers/some_init.rb
RUNNER = Runner.instance

#app/controllers/some_controller.rb
class SomeController < ApplicationController
  def run
    RUNNER.run {
      loop do
        Rails.logger.debug "backend thread running #{Time.zone.now}"
        sleep(5)
      end
    }
  end

  def status
    Rails.logger.debug RUNNER.inspect
    RUNNER.is_running?
  end
end

#lib/runner.rb
require 'singleton'
class Runner
  include Singleton
  def initialize
    @running = false
    @mutex = Mutex.new
  end

  def run(&blk)
    @mutex.synchronize do
      return if @running #only one job run is allowed at anytime

      @running = true
      @thr = Thread.new(self) do |r|
        blk.call
        r.done
      end 
    end
  end

  def is_running?
    @mutex.synchronize{ @running }
  end

  private
  def done
    @mutex.synchronize {@running = false}
  end
end

Upvotes: 0

Views: 740

Answers (1)

tadman
tadman

Reputation: 211560

Short answer: You can't do this.

The long answer is that Rails consists of one or more processes which serve incoming requests. Starting a thread in one of these processes randomly is not going to make that is_running? flag become true in any process other than the one in which the run method was triggered.

Most Rails hosting environments will create and destroy processes frequently with no guarantee that your process will live longer than the request it is currently processing. This means your thread is likely to get killed unexpectedly.

Rails is strictly a request-response system. If you need a background process, you must create this as a stand-alone script, not something engaged through Rack.

It is possible to create long-running scripts that run inside the Rails environment, for instance, started with rails runner, but these must be started independently of your application. Normally you use a process supervisor like systemctl or god to kick this off and keep it running.

Depending on your use case, a job processing system like delayed job might be a better fit for you. This is great for performing intermittent "background tasks" that don't fit within the web-facing Rails process model.

Upvotes: 4

Related Questions