LoginSignup
18
17

More than 5 years have passed since last update.

[Ruby]プログラムの2重起動を防ぐ

Last updated at Posted at 2013-10-29

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
18
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
17