DelayedJobのhandlerをRubyオブジェクトに変換する


背景

DelayedJobを利用して定期実行しているタスクがあるのですが、処理時間が長いと全く同じタスクが並列して走ってしまうことがありました。
(cronを利用しないのはサーバーのリソースが枯れてしまわないように数を制限しているため)

既に実行されている(locked_atに時間が入っているもの)のhandlerから何のタスクが実行されているか分かれば後からキューイングされたジョブの実行をスキップする処理ができるなと思い調べたのですが変換方法が調べても出てこなかったので記事にしました。

handlerの取り出し

まずは結論から書きたいと思います。

handler = Delayed::Job.first
YAML.load_dj(handler)

上記のようにYAML#load_djを利用することでDelayed::Jobを使ってenqueueしたジョブのhandlerをRubyのオブジェクトに変換することができます。
できたオブジェクトはenqueuしたときのクラスなので、例のようにクラス変数のnameなどを取り出したい場合は元のクラスで参照できるようにattr_readerなどの設定をする必要があります。

class.rb
class A
  attr_reader :name
  def initialize
    @id = 'id'
    @name = 'name'
  end

  def enqueue
    Delayed::Job.enqueue~~~
  end
end

A.new.enqueue
handler = Delayed::Job.first
task = YAML.load_dj(handler)
=> #<A ~~~~~>
task.name
=> name

load_djの実装

ちなみにこの取り出し方はDelayed::Jobでワーカーが取り出す際に利用しているものでこちらから確認できます。

delayed_job/base.rb
def payload_object
  @payload_object ||= YAML.load_dj(handler)
rescue TypeError, LoadError, NameError, ArgumentError, SyntaxError, Psych::SyntaxError => e
  raise DeserializationError, "Job failed to load: #{e.message}. Handler: #{handler.inspect}"
end

また、YAMLモジュールをオーバライドしている箇所はこちらになります

delayed_job/psych_ext.rb
module Psych
  def self.load_dj(yaml)
    result = parse(yaml)
    result ? Delayed::PsychExt::ToRuby.create.accept(result) : result
  end
end

最後に

もう少し実装を抽象化できればDelayed::Jobに実装しても良い機能なきもするので実装を考えてみたいと思います。
本記事が誰かの役に立てれ立てれば嬉しいです