Schlafende Daemonen und Signale

19/04/2008 - 21:17 von Michael Schuerig | Report spam
Ein Daemon, in Ruby geschrieben, soll periodisch (alle 5 Minuten etwa)
eine Aufgabe ausführen. Durch ein Signal soll er dazu veranlasst werden
können, sich unmittelbar, auch wenn die Wartezeit noch nicht abgelaufen
ist, an die Arbeit zu machen.

Mein bester Ansatz sieht bisher so aus

require 'rubygems'
require 'daemons'

Daemons.run_proc('daemon') do
$next_run = Time.now

Signal.trap('HUP') do |signo|
$next_run = Time.now # X
end

loop do
if $next_run < Time.now # A
$next_run = Time.now + (5 * 60) # B
do_something
end
sleep 2
end
end

(Daemons.run_proc startet den Block in einem eigenen Daemon-Prozess.)

Zufrieden bin ich damit aber nicht. Der Code enthàlt (mindestens) eine
Race Condition, nàmlich wenn die Zeilen A, X, B in dieser Reihenfolge
ausgeführt werden, was nicht ausgeschlossen ist, da der Signal Handler
asynchron ausgeführt wird.

Wenn möglich würde ich den Prozess auch gerne nur dann aufwecken, wenn
es nötig ist, also nach Ablauf des 5min-Intervals oder wenn ein SIGHUP
rein kommt.

Über sachdienliche Hinweise würde ich ich freuen.

Michael

Michael Schuerig
mailto:michael@schuerig.de
http://www.schuerig.de/michael/
 

Lesen sie die antworten

#1 Robert Klemme
20/04/2008 - 13:06 | Warnen spam
On 19.04.2008 21:17, Michael Schuerig wrote:
Ein Daemon, in Ruby geschrieben, soll periodisch (alle 5 Minuten etwa)
eine Aufgabe ausführen. Durch ein Signal soll er dazu veranlasst werden
können, sich unmittelbar, auch wenn die Wartezeit noch nicht abgelaufen
ist, an die Arbeit zu machen.

Mein bester Ansatz sieht bisher so aus

require 'rubygems'
require 'daemons'

Daemons.run_proc('daemon') do
$next_run = Time.now

Signal.trap('HUP') do |signo|
$next_run = Time.now # X
end

loop do
if $next_run < Time.now # A
$next_run = Time.now + (5 * 60) # B
do_something
end
sleep 2
end
end

(Daemons.run_proc startet den Block in einem eigenen Daemon-Prozess.)

Zufrieden bin ich damit aber nicht. Der Code enthàlt (mindestens) eine
Race Condition, nàmlich wenn die Zeilen A, X, B in dieser Reihenfolge
ausgeführt werden, was nicht ausgeschlossen ist, da der Signal Handler
asynchron ausgeführt wird.

Wenn möglich würde ich den Prozess auch gerne nur dann aufwecken, wenn
es nötig ist, also nach Ablauf des 5min-Intervals oder wenn ein SIGHUP
rein kommt.

Über sachdienliche Hinweise würde ich ich freuen.



Ich würde do_something im Signal-Handler aufrufen und dann die Schleife
anders machen:

loop do
if Time.now >= $next_run
do_something
$next_run += 5 * 60
# wenn do_something lange laufen kann,
# kann man auch das machen:
# irb$next_run += 5 * 60 while $next_run < Time.now
end
sleep $next_run - Time.now
end

damit du nicht stàndig CPU verbràtst (wg. des "sleep 2"). Zusàtzlich
würde ich noch Vorsorge treffen, dass do_something thread safe ist bzw.
der Signal-Handler und der Haupt-Code nicht parallel laufen (siehe unten).

Eine ganz andere Möglichkeit: man nimmt keinen Dàmon, sondern überlàsst
cron den regelmàßigen Aufruf eines Programms, das do_something's Arbeit
macht. Dann stellt man über ein File-Lock sicher, dass es nicht zweimal
parallel làuft und anstatt ein Signal zu senden, ruft man das Programm
einfach direkt auf. Ich finde diese Variante viel attraktiver, wenn
dein Betriebssystem cron hat. Das Scheduling ist da schon lange
implementiert und getestet und du brauchst einen Prozess weniger, der
stàndig làuft. Ob das geht hàngt natürlich davon ab, was dein Dàmon so
macht.

Ciao

robert



#!/bin/env ruby

require 'thread'

class Worker

def initialize
@lock = Mutex.new
end

def do_something
if @lock.try_lock
begin
puts "#{Thread.current.inspect} working..."
sleep 1
ensure
@lock.unlock
end
else
puts "#{Thread.current.inspect} ignoring"
end
end
end

work = Worker.new

(1..4).map do
Thread.new do
10.times do
work.do_something
sleep 0.5
end
end
end.each {|th| th.join}

Ähnliche fragen