君の瞳はまるでルビー - Ruby 関連まとめサイト

clockwork について

最終更新: 2015-04-16 (木) 14:08:59 (2905d)

cron の代替となる機能を単独で提供してくれる軽量のツール/ライブラリです。

clockwork のメインサイトを読んだだけではわからないことがたくさんあったので、 clockwork のソースを実際に読んで情報を得ている部分がありますし、自分で考えて書いた部分も結構あります。

何か意見があればコメント欄に記載をお願いします。

関連記事

clockwork をインストールする

以下のコマンドを実行して clockwork gem をインストールします。

gem install clockwork

バージョン 0.7.0 からネイティブコードを含むパッケージを使うようになったため、コンパイルエラーがでる場合は DEVELOPMENT KIT のセットアップを行う必要があります。

もしくは以下の方法で 0.6.2 をインストールする方法もあります。

gem install clockwork --version "= 0.6.2"

clockwork が提供してくれるメソッドについて

clockwork は以下のメソッドを提供してくれます。

メソッド名概要
every定期実行するジョブを定義するメソッド。
handlerジョブ実行時に呼び出される処理を定義するメソッド。
Numeric#seconds, Numeric#minutes, Numeric#hour, Numeric#day時間の指定を書きやすくしてくれる拡張です。
configure*1Clockwork の初期化時に呼び出され、Clockwork のマルチスレッド/ロギング/タイムゾーンに関する設定を定義するメソッド。

これらのメソッドを組合せて定期実行をプログラムし、clockwork コマンドでそれを実行します。

サンプルソース

require 'clockwork'

module Clockwork
  handler do |job|
    puts "Running #{job}"
  end

  every(10.seconds, 'frequent.job')
  every(3.minutes, 'less.frequent.job')
  every(1.hour, 'hourly.job')

  every(1.day, 'midnight.job', :at => '00:00')
end

解説

基本

基本は、以下の通りです。

  • handler にジョブ実行時の処理を定義する。
  • every でジョブを定義する。
  • every でジョブの実行タイミングを指定する際に Numeric#seconds, Numeric#minutes, Numeric#hour, Numeric#day を使う。
  • module Clockwork は handler, every を呼びやすくするためのもので、特別な事情がなければそのままで良い(必要な状況は後述)。

every でジョブ定義

サンプルでは4つのジョブを定義しています。

実行タイミングジョブ名
10 秒毎に実行frequent.job(頻繁なジョブ)
3 分毎に実行less.frequent.job(ちょっと頻繁なジョブ)
1 時間毎に実行hourly.job(1時間毎のジョブ)
毎日夜中の12時に実行midnight.job(真夜中のジョブ*2

handler でジョブ実行

every で定義したタイミングで handler のブロックが呼び出されます。

  • ジョブを実行できる handler は1つだけ。
  • job は every の 2 つ目の引数で指定したオブジェクト。

サンプルでは全ての every 呼び出しで文字列を渡しているので 10 秒毎に

Running frequent.job

と表示されます。他のジョブも同様です。

1.day を除き、他のジョブは起動時にまず最初に実行され、そこから時間の計測がはじまります。

every や handler の名前が他と衝突するとき

もしも every や handler が他の機能の名前と衝突する場合は、以下のようにして使います。

  • module Clockwork を削除する。
  • every の代わりに Clockwork::every を使う。
  • handler の代わりに Clockwork::handler を使う。

clockwork を動かす

以下のコマンドを実行して clockwork を起動します。

clockwork clock.rb
  • clock.rb はよく使われる名前なだけで、ファイルの名前は任意である。
  • clockwork コマンドは指定されたファイルを Ruby のプログラムとして実行し、handler と every で指定された情報に基づきジョブを実行し続けてくれる。
    • clockwork は clock.rb を動かしてくれる Ruby で作られた単なるラッパープログラムである。
    • clockwork は指定されたファイルを Ruby のプログラムとして実行しているだけなので、clock.rb は handler と every の呼び出しさえすれば、他の Ruby のプログラムとの違いはない。

clockwork を止める

SIGINT シグナルを送れば Exit と表示されて正常終了します。Windows なら Ctrl + C を押してください。

その他、StandardError 以外の例外が発生すると止まります。

基本的なプログラムの書き方

handler にすべてのジョブが集まります。

ジョブが1種類だけであれば handler に単純に処理を書けば良いですが、複数種類のジョブを扱うには工夫が必要です。

複数のジョブを処理するにはいくつかの書き方があります。以下に事例を挙げたいと思います。

  1. handler ブロックに case 文を書いて処理を振り分ける。
  2. lambda/Proc/Fiber/ を使って every メソッド呼び出し毎に処理を定義し、handler ブロッグで呼び出す。
  3. 個々の処理をクラスとして定義し every メソッド呼び出し毎に対応するオブジェクトを生成して渡し、ポリモフィズムを使って呼び出し先を自動で分ける。

case 文の利用

require 'clockwork'

module Clockwork
  handler do |job|
    case job
    when 'frequent.job'
      # 10 秒毎の処理
    when 'less.frequent.job'
      # 3 分毎の処理
    when 'hourly.job'
      # 1 時間毎の処理
    when 'midnight.job'
      # 夜中の処理(なんだかエロい…)
    end
  end

  every(10.seconds, 'frequent.job')
  every(3.minutes, 'less.frequent.job')
  every(1.hour, 'hourly.job')

  every(1.day, 'midnight.job', :at => '00:00')
end

単純な場合は、これでも良いと思います。

lambda/Proc/Fiber の利用

lambda と Proc の呼び出しは call という同じ名前のメソッドなので一緒に使えます。

しかし、Fiber の呼び出しは resume なので lambda と Proc と一緒に使うことはできないので、

  1. lambda と Proc で全てのジョブを定義する。
  2. Fiber で全てのジョブを定義する。

のいずれかの場合に利用できる方法です。

番外編として

  • Proc と Fiber を case で振り分けて呼び出すよう handler を定義する。

という方法もありますが、恐らく 1 番の方法を利用する機会が多いと思うので、その事例を示します。

  • every 呼び出しに lambda や Proc を使って処理を定義する。
  • handler で call を呼び出して、それぞれのブロックを呼び出す。
  • fiber を使う場合、handler では call ではなく resume を使う。
require 'clockwork'

module Clockwork
  handler do |job|
    job.call
  end

  # こういう書き方もOK 
  every(
    10.seconds,
    lambda do
      # 10 秒毎の処理
    end
  )

  # こういう書き方もOK
  less_frequent_job = lambda do
    # 3 分毎の処理
  end
  every(3.minutes, less_frequent_job)

  # こういう書き方もOK
  every(
    1.hours,
    Proc.new do
      # 1 時間毎の処理
    end
  )

  every(
    1.day,
    lambda do
      # 夜中の処理(どう書こうとなんだかエロい…)
    end, :at => '00:00'
  )

ポリモフィズムの利用

この方法が一番汎用的なので、プログラムが大きくなればこの方法を採ることになると思います。

  • 個々のジョブをファイル/クラスに分けて定義する。
  • clock.rb から require する。
  • クラスで定義するメソッドの名前は同じにする(事例では call としているが、統一されていれば何でも良い。lambda や Proc と組合せて使うことも考慮すると call が一番いいと思う)。
  • every でそれぞれのオブジェクトを生成して渡す。
  • handler で call を呼ぶ。
  • clock.rb
require 'clockwork'

require 'frequent_job'
require 'less_frequent_job'
require 'hourly_job'
require 'midnight_job'

module Clockwork
  handler do |job|
    job.call
  end

  every(10.seconds, FrequentJob.new)
  every(3.minutes, LessFrequentJob.new)
  every(1.hour, HourlyJob.new)

  every(1.day, MidnightJob.new, :at => '00:00')
end
  • frequent_job.rb
class FrequentJob
  def call
    # 10 秒毎の処理
  end
end
  • less_frequent_job.rb
class LessFrequentJob
  def call
    # 3 分毎の処理
  end
end
  • hourly_job.rb
class HourlyJob
  def call
    # 1 時間毎の処理
  end
end
  • midnight_job.rb
class MidnightJob
  def call
    # 夜中の処理(ファイルが分けられ密閉度上昇…、さらにエロい…)
  end
end

備考

clockwork はシングルスレッドで動作するため、並列に動作させたい場合は :thread オプションを利用する

clockwork はシングルスレッドで動作します。handler の呼び出しもシングルスレッドです。

従って、handler 内でループなどの長期処理を実行した場合、その他のジョブの実行は待たされることになります。

every で指定した個々のジョブの実行タイミングは、前回実行を開始した時間を基点としてカウントされます。

特定の長期ジョブにより handler が占有され、開始時刻を過ぎたジョブは、長期ジョブの終了を契機に一気に実行されることになります。

このように特定のジョブにより handler が占有されないようにするには、スレッドを利用する必要があります。

version 0.5.1 からジョブ定義に :thread オプションを指定することで別スレッドでハンドラを動かす機能が追加されたようです。

例えば以下のように書くことで該当のジョブを別スレッドにできます。

# 長期処理
every(10.seconds, 'long_time.job', :thread => true)

最大スレッド数は configure にて設定が可能です。

configure do |config|
  config[:max_threads] = 100
end

起動時にスレッド数が最大数を超えた場合、ジョブは実行されず、ログにその旨のメッセージが出力されます。

config[:max_threads] の初期値は 10 です。

この max_threads の値は、Clockwork が管理するスレッドだけではなく、全てのスレッド数を監視しているので、自分で独自に内部処理でスレッドを作っている場合はそのスレッドもカウントされます。

以降の情報は古いので不要かもしれませんが残しておきます。version 0.5.0 以前にマルチスレッドの機能はないので、自分でそれぞれのジョブを handler 内でスレッドにして実行する必要がありました。

require 'clockwork'

module Clockwork
  handler do |job|
    Thread.new do
      # ジョブごとの処理を実行する。
      # Thread.pass を適度に呼び、他のスレッドに処理を渡す。
      # 同一のジョブが多重で実行されないようにするには適宜 Mutex を利用する。
    end
  end

  every(10.seconds, 'frequent.job')
  every(3.minutes, 'less.frequent.job')
  every(1.hour, 'hourly.job')

  every(1.day, 'midnight.job', :at => '00:00')
end

コメント

本ページの内容に関して何かコメントがある方は、以下に記入してください。

最新の10件を表示しています。 コメントページを参照

  • 勉強させてもらっています。気になったのですが、「以下のコマンドを実行して foreman を...」となっていますが、ここは clockwork の間違いかと。 -- taka 2012-02-22 (水) 22:16:42
  • ありがとうございます!その通りです!直しました^^ -- トゥイー 2012-02-23 (木) 16:41:56
  • 本家サイトでは include Clockwork の慣習をやめて module Clockwork の利用を推奨している(?)と思われるので、それに合わせてサンプルコードを修正しました。 -- トゥイー 2014-01-08 (水) 18:31:39
お名前: