我如何确保一次只运行一个Ruby脚本实例?

时间:2009-03-19 10:30:35

标签: ruby linux

我有一个每五分钟在cron上运行一个进程。通常,运行只需几秒钟,但有时需要几分钟。我想确保一次只运行一个版本。

我尝试了一种显而易见的方式......

File.open("/tmp/indexer_lock.tmp",'w') do |f|
  exit unless f.flock(File::LOCK_EX)
end

...但它没有测试是否可以获得锁定,它会在锁定被释放之前阻塞。

知道我错过了什么吗?我宁愿不使用ps来破解某些东西,但这是另一种选择。

10 个答案:

答案 0 :(得分:23)

我知道这已经过时了,但是对于任何有兴趣的人来说,有一个非阻塞常量可以传递给flock,以便它返回而不是阻塞。

File.new("/tmp/foo.lock").flock( File::LOCK_NB | File::LOCK_EX )

slhck

的更新 如果进程收到锁定,

flock将返回true,否则返回false。因此,为了确保一次只运行一个进程,您只想尝试获取锁定,如果您无法执行,则退出。它就像在我上面的代码行前放exit unless一样简单:

exit unless File.new("/tmp/foo.lock").flock( File::LOCK_NB | File::LOCK_EX )

答案 1 :(得分:4)

根据您的需要,这应该可以正常工作,不需要在任何地方创建另一个文件。

exit unless DATA.flock(File::LOCK_NB | File::LOCK_EX)

# your script here

__END__
DO NOT REMOVE: required for the DATA object above.

答案 2 :(得分:3)

您可以创建和删除临时文件并检查是否存在此文件。 请检查这个问题的答案: one instance shell script

答案 3 :(得分:3)

虽然这不是直接回答你的问题,但如果我是你,我可能会写一个守护程序脚本(你可以使用http://daemons.rubyforge.org/

您可以让您的索引器(假设其indexer.rb)通过名为script / index的包装脚本运行,例如:

require 'rubygems'
require 'daemons'

Daemons.run('indexer.rb')

除了指定睡眠间隔

之外,你的索引器几乎可以做同样的事情
loop do
   # code executing your indexing 

   sleep INDEXING_INTERVAL
end

这就是与队列服务器串联的作业处理器通常如何运行。

答案 4 :(得分:3)

对于这种情况,有一个lockfile gem。我以前用过它而且很简单。

答案 5 :(得分:2)

如果您使用cron,可能更容易在cron调用的shell脚本中执行类似的操作:

#!/usr/local/bin/bash
#

if ps -C $PROGRAM_NAME &> /dev/null ; then
   : #Program is already running.. appropriate action can be performed here (kill it?)
else
   #Program is not running.. launch it.
   $PROGRAM_NAME
fi

答案 6 :(得分:2)

这是一个单行代码,应该在任何Ruby脚本的顶部都可以使用:

exit unless File.new(__FILE__)).tap {|f| f.autoclose = false}.flock(File::LOCK_NB | File::LOCK_EX)

原始代码有两个问题。

首先,它被阻止的原因是#flock的呼叫丢失了File::LOCK_NB

锁定时不阻塞。可以结合 与其他锁定选项一起使用逻辑或。

第二,如果一个File对象被关闭(无论是在上面代码中的#open块的末尾,还是通过显式#close或隐式自动关闭)当文件被垃圾回收时),基础文件描述符被关闭并且锁被释放。为防止这种情况,您可以设置#autoclose = false

答案 7 :(得分:1)

好的,从@ shodanex的指针处理笔记,这就是我所拥有的。我稍微揉了一下(虽然我不知道Ruby中的触摸模拟)。

tmp_file = File.expand_path(File.dirname(__FILE__)) +  "/indexer.lock"
if File.exists?(tmp_file)
  puts "quitting"
  exit
else
  `touch #{tmp_file}`
end

.. do stuff ..

File.delete(tmp_file)

答案 8 :(得分:0)

你能否将File :: LOCK_NB添加到你的锁中,使其无阻塞(即如果它无法获得锁定则失败)

这适用于C,Perl等。

答案 9 :(得分:0)

在更高级别,您可能会发现lock_method宝石有用:

def the_method_my_cron_job_calls
  # something really expensive
end
lock_method :the_method_my_cron_job_calls

它默认使用存储在本地文件系统上的锁文件(上面讨论的内容),但您也可以配置远程锁存储:

LockMethod.config.storage = Redis.new([...]) # a remote RedisToGo instance, perhaps?

也...

def the_method_my_cron_job_calls
  # something really expensive
end
lock_method :the_method_my_cron_job_calls, (60*60) # automatically expire lock after an hour