ruby プログラムの2重起動を防ぐ方法として 次の記事
http://altarf.net/computer/ruby/1439
を見つけたのですが、ちょっと複雑すぎる気がする。
そこで、次のコードを書いてみました。(ruby-1.9.3, MacOS 上)
--- cat single_task.rb ------
# -*- coding: utf-8 -*-
######################
# 処理が同時に複数 走らないようにする。
#
# すでに処理が走っていれば、その旨を表示して exit 1 する。
# 他に処理が走っていなければ、 処理を行う。
# 処理が正常処理したなら、exit 0 する。
# 処理が正常終了しなければ exit 1 する。
#
# 実行例
# -------
# 次のように起動すれば、exit 値も確認できる。
# $ ruby single_task.rb; echo $?
#
# See http://doc.ruby-lang.org/ja/1.9.3/class/File.html
# http://without-truth.hatenablog.com/entry/2012/11/08/111334
# http://d.hatena.ne.jp/unageanu/20080127/1201423758
######################
# p Signal.list でシグナル一覧が得られるけれど...
# ここでは :INT, :KILL, :TERM にだけ対応する。
# Ctrl-C が押された場合の処理
Signal.trap(:INT) {
puts "### Signal :INT"
exit 1
}
# kill -9 された場合の処理
Signal.trap(:KILL) {
puts "### Signal :KILL"
exit 1
}
# kill された場合の処理
Signal.trap(:TERM) {
puts "### Signal :TERM"
exit 1
}
def single_run(&task)
begin
ret = 1
File.open('lockfile', 'w') do |f|
# ロック開始
# File::LOCK_NB を指定しているので既にロックされている場合は
# ブロッキングされずに false になる
if f.flock(File::LOCK_EX | File::LOCK_NB)
puts '### lock'
ret = task.call
# ロック解除
f.flock(File::LOCK_UN)
puts '### unlock'
else
# 既にロックされている
puts "### alreadly runnng..."
end
end
rescue => ex
puts "### error #{ex.message}"
end
ret
end
if $0 == __FILE__
def my_task(arg = 1)
puts "sleep #{arg} sec(s) ..."
sleep arg
0 # Success
end
ret = single_run { my_task ARGV[0].to_f }
exit ret
end
#--- Ed of File ---
此処 Qiita には shell-script の多重起動を制御する方法の記事が 複数 投稿されています。
そちらのほうが確実性が高いなら、
ruby プログラムを shell-script で 包んで、shell-script のレベルで多重起動制御をする
のも有り と思っていますが...
簡単な rspec コードも書いてみました。
$ rspec single_task_spec.rb
### sleep 1 ...
.### lock
### sleep 1 ...
### unlock
.### lock### alreadly runnng...
### sleep 3 ...
### unlock
### lock
### sleep 1 ...
### unlock
.
Finished in 6.01 seconds
3 examples, 0 failures
--- cat single_task_spec.rb ------
# -*- coding: utf-8 -*-
require 'rspec'
require './single_task.rb'
describe 'apas_server' do
def sample_task(s = 1)
puts "### sleep #{s} ..."
sleep s
0
end
specify "run normal" do
ret = sample_task 1
expect(ret).to eq(0)
end
specify "run as single_task" do
ret = single_run { sample_task 1 }
expect(ret).to eq(0)
end
specify "run multi as single_task" do
t = Thread.new do
ret = single_run { sample_task 3 }
expect(ret).to eq(0)
# 上の処理が終わったあとなら、正常に実行できる。
ret = single_run { sample_task 1 }
expect(ret).to eq(0)
end
# 上の処理が実行中なので、 ret = 1 になる。
ret = single_run { sample_task 1 }
expect(ret).to eq(1)
t.join
end
# TODO: Ctrl-C, kill などシグナルが送られた時の挙動をテストする事。
end