daemonic 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/.travis.yml +8 -0
- data/{LICENSE.txt → MIT-LICENSE.txt} +1 -1
- data/README.md +108 -78
- data/Rakefile +11 -4
- data/daemonic.gemspec +7 -4
- data/examples/init-d.sh +37 -0
- data/examples/rss +41 -0
- data/features/support/env.rb +1 -0
- data/features/worker.feature +43 -0
- data/lib/daemonic.rb +40 -8
- data/lib/daemonic/cli.rb +147 -40
- data/lib/daemonic/daemon.rb +126 -0
- data/lib/daemonic/pool.rb +43 -82
- data/lib/daemonic/producer.rb +49 -0
- data/lib/daemonic/version.rb +1 -1
- metadata +68 -31
- data/bin/daemonic +0 -6
- data/lib/daemonic/configuration.rb +0 -110
- data/lib/daemonic/logging.rb +0 -14
- data/lib/daemonic/master.rb +0 -119
- data/lib/daemonic/pidfile.rb +0 -64
- data/lib/daemonic/worker.rb +0 -93
- data/test/config +0 -6
- data/test/crappy_daemon.rb +0 -1
- data/test/integration_test.rb +0 -155
- data/test/test_daemon.rb +0 -10
data/lib/daemonic/pidfile.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
|
3
|
-
module Daemonic
|
4
|
-
class Pidfile
|
5
|
-
include FileUtils
|
6
|
-
include Logging
|
7
|
-
|
8
|
-
attr_reader :pid, :config, :index
|
9
|
-
|
10
|
-
def initialize(options = {})
|
11
|
-
@pid = options.fetch(:pid)
|
12
|
-
@config = options.fetch(:config)
|
13
|
-
@index = options[:index]
|
14
|
-
end
|
15
|
-
|
16
|
-
def write
|
17
|
-
mkdir_p(File.dirname(filename))
|
18
|
-
|
19
|
-
if File.exist?(filename)
|
20
|
-
existing_pid = File.open(filename, 'r').read.chomp
|
21
|
-
running = Process.getpgid(existing_pid) rescue false
|
22
|
-
if running
|
23
|
-
fatal "Error: PID file already exists at `#{filename}` and a process with PID `#{existing_pid}` is running"
|
24
|
-
exit 1
|
25
|
-
else
|
26
|
-
debug "Warning: cleaning up stale pid file at `#{filename}`"
|
27
|
-
write!
|
28
|
-
end
|
29
|
-
else
|
30
|
-
debug "Writing pidfile #{filename} with pid #{pid}"
|
31
|
-
write!
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def clean
|
36
|
-
if File.exist?(filename)
|
37
|
-
existing_pid = File.open(filename, 'r').read.chomp
|
38
|
-
if existing_pid == pid.to_s
|
39
|
-
debug "Cleaning up my own pid file at `#{filename}`"
|
40
|
-
rm(filename)
|
41
|
-
else
|
42
|
-
debug "Pid file `#{filename}` did not belong to me, so not cleaning. I am #{Process.pid}, and the pid file says #{existing_pid}"
|
43
|
-
end
|
44
|
-
else
|
45
|
-
debug "Pid file disappeared from `#{filename}`"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def filename
|
52
|
-
if index
|
53
|
-
config.pidfile.to_s.sub(/\.([^\.]+)\z/, ".#{index}.\\1")
|
54
|
-
else
|
55
|
-
config.pidfile
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def write!
|
60
|
-
File.open(filename, 'w') { |f| f.write(pid) }
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
data/lib/daemonic/worker.rb
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
module Daemonic
|
2
|
-
class Worker
|
3
|
-
include Logging
|
4
|
-
|
5
|
-
attr_reader :index, :config
|
6
|
-
|
7
|
-
def initialize(options)
|
8
|
-
@index = options.fetch(:index)
|
9
|
-
@config = options.fetch(:config)
|
10
|
-
end
|
11
|
-
|
12
|
-
def start
|
13
|
-
@pid = Process.spawn(
|
14
|
-
{"DAEMON_WORKER_NUMBER" => index.to_s},
|
15
|
-
*Array(config.command),
|
16
|
-
{:chdir => config.working_dir}
|
17
|
-
)
|
18
|
-
sleep 0.1
|
19
|
-
info "#{to_s} Starting."
|
20
|
-
pidfile.write
|
21
|
-
wait_for { running? }
|
22
|
-
info "#{to_s} Started!"
|
23
|
-
end
|
24
|
-
|
25
|
-
def stop
|
26
|
-
Process.kill("TERM", pid)
|
27
|
-
info "#{to_s} Stopping."
|
28
|
-
Process.waitpid(pid)
|
29
|
-
rescue Errno::ECHILD, Errno::ESRCH
|
30
|
-
warn "#{to_s} Already stopped."
|
31
|
-
ensure
|
32
|
-
pidfile.clean
|
33
|
-
@pid = nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def restart
|
37
|
-
info "#{to_s} Restarting."
|
38
|
-
stop
|
39
|
-
start
|
40
|
-
end
|
41
|
-
|
42
|
-
def hup
|
43
|
-
Process.kill("HUP", pid)
|
44
|
-
end
|
45
|
-
|
46
|
-
def monitor
|
47
|
-
if not running?
|
48
|
-
warn "#{to_s} Not running."
|
49
|
-
start
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def running?
|
54
|
-
if @pid
|
55
|
-
!Process.waitpid(pid, Process::WNOHANG) rescue false
|
56
|
-
else
|
57
|
-
false
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def pid
|
64
|
-
@pid || raise("unknown pid")
|
65
|
-
end
|
66
|
-
|
67
|
-
def pidfile
|
68
|
-
Pidfile.new(
|
69
|
-
pid: pid,
|
70
|
-
config: config,
|
71
|
-
index: index,
|
72
|
-
)
|
73
|
-
end
|
74
|
-
|
75
|
-
def wait_for(timeout=2)
|
76
|
-
deadline = Time.now + timeout
|
77
|
-
until Time.now >= deadline
|
78
|
-
result = yield
|
79
|
-
if result
|
80
|
-
return
|
81
|
-
else
|
82
|
-
sleep 0.1
|
83
|
-
end
|
84
|
-
end
|
85
|
-
warn "#{to_s} It took too long to start."
|
86
|
-
end
|
87
|
-
|
88
|
-
def to_s
|
89
|
-
"<Worker:#{index} pid:#{@pid.inspect} running:#{running?.inspect}>"
|
90
|
-
end
|
91
|
-
|
92
|
-
end
|
93
|
-
end
|
data/test/config
DELETED
data/test/crappy_daemon.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
exit 1
|
data/test/integration_test.rb
DELETED
@@ -1,155 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require "thread"
|
3
|
-
|
4
|
-
class TestIntegration < MiniTest::Unit::TestCase
|
5
|
-
|
6
|
-
def test_integration
|
7
|
-
master_pid = daemonic %Q|--workers 5 --pid tmp/test.pid --name daemon-test --command "ruby test/test_daemon.rb" --log #{LOGFILE}|
|
8
|
-
perform_tests(master_pid)
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_with_config_file
|
12
|
-
master_pid = daemonic %Q|--config test/config|
|
13
|
-
perform_tests(master_pid)
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_with_failing_daemon
|
17
|
-
master_pid = daemonic %Q|--pid tmp/test.pid --name daemon-test --command "ruby test/crappy_daemon.rb" --log #{LOGFILE}|
|
18
|
-
sleep 0.2
|
19
|
-
wait_for(10) { not running? master_pid }
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_start_daemonic_programmatically
|
23
|
-
$LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
|
24
|
-
require 'daemonic'
|
25
|
-
Thread.new {
|
26
|
-
Daemonic.start(
|
27
|
-
name: "daemon-test",
|
28
|
-
workers: 5,
|
29
|
-
command: "ruby test/test_daemon.rb",
|
30
|
-
logfile: LOGFILE,
|
31
|
-
pidfile: "tmp/test.pid",
|
32
|
-
)
|
33
|
-
}
|
34
|
-
sleep 0.2
|
35
|
-
master_pid = Integer(File.open("tmp/test.pid").read)
|
36
|
-
perform_tests(master_pid)
|
37
|
-
end
|
38
|
-
|
39
|
-
def setup
|
40
|
-
FileUtils.mkdir_p(File.dirname(LOGFILE))
|
41
|
-
FileUtils.rm(LOGFILE) if File.exist?(LOGFILE)
|
42
|
-
end
|
43
|
-
|
44
|
-
def teardown
|
45
|
-
spawned_commands.each { |pid| Process.kill("TERM", pid) }
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def running?(pid)
|
51
|
-
Process.getpgid(pid) rescue false
|
52
|
-
end
|
53
|
-
|
54
|
-
def perform_tests(master_pid)
|
55
|
-
|
56
|
-
pids_of_workers = []
|
57
|
-
wait_for {
|
58
|
-
pids_of_workers = find_worker_pids
|
59
|
-
pids_of_workers.size == 5
|
60
|
-
}
|
61
|
-
|
62
|
-
sleep 1
|
63
|
-
|
64
|
-
# restart
|
65
|
-
signal "USR2", master_pid
|
66
|
-
|
67
|
-
sleep 1
|
68
|
-
|
69
|
-
wait_for {
|
70
|
-
new_pids_of_workers = find_worker_pids
|
71
|
-
all_are_different?(new_pids_of_workers, pids_of_workers)
|
72
|
-
}
|
73
|
-
assert_equal 5, find_worker_pids.size
|
74
|
-
|
75
|
-
# master pid file
|
76
|
-
file = "tmp/test.pid"
|
77
|
-
assert File.exist?(file), "Missing master pidfile at #{file}"
|
78
|
-
|
79
|
-
# worker pid files
|
80
|
-
(0...5).each do |num|
|
81
|
-
file = "tmp/test.#{num}.pid"
|
82
|
-
assert File.exist?(file), "Missing worker pidfile at #{file}"
|
83
|
-
end
|
84
|
-
|
85
|
-
# increase workers on the fly
|
86
|
-
signal "TTIN", master_pid
|
87
|
-
assert_equal 6, find_worker_pids.size
|
88
|
-
|
89
|
-
# decrease workers on the fly
|
90
|
-
signal "TTOU", master_pid
|
91
|
-
assert_equal 5, find_worker_pids.size
|
92
|
-
|
93
|
-
# monitor crashing workers
|
94
|
-
pid_to_kill = find_worker_pids.first
|
95
|
-
signal "TERM", pid_to_kill
|
96
|
-
sleep 0.2
|
97
|
-
assert_equal 5, find_worker_pids.size
|
98
|
-
|
99
|
-
# shutdown
|
100
|
-
signal "TERM", master_pid
|
101
|
-
workers_remaining = []
|
102
|
-
wait_for {
|
103
|
-
workers_remaining = find_worker_pids
|
104
|
-
workers_remaining.size == 0
|
105
|
-
}
|
106
|
-
end
|
107
|
-
|
108
|
-
SCRIPT_NAME = "test_daemon.rb"
|
109
|
-
LOGFILE = "log/test.log"
|
110
|
-
|
111
|
-
def spawned_commands
|
112
|
-
@spawned_commands ||= []
|
113
|
-
end
|
114
|
-
|
115
|
-
def find_worker_pids
|
116
|
-
find_pids_for(SCRIPT_NAME)
|
117
|
-
end
|
118
|
-
|
119
|
-
def daemonic(command)
|
120
|
-
cmd = "./bin/daemonic #{command}"
|
121
|
-
puts "\n#{cmd}"
|
122
|
-
pid = spawn(cmd)
|
123
|
-
spawned_commands << pid
|
124
|
-
pid
|
125
|
-
end
|
126
|
-
|
127
|
-
def find_pids_for(filter)
|
128
|
-
processes = `ps u`.split("\n")
|
129
|
-
found = processes.select { |line| line.include?(filter) }
|
130
|
-
found.map { |line| line.split(/\s+/, 4)[1].to_i }
|
131
|
-
end
|
132
|
-
|
133
|
-
def signal(type, pid)
|
134
|
-
Process.kill(type, pid)
|
135
|
-
sleep 0.1
|
136
|
-
end
|
137
|
-
|
138
|
-
def wait_for(timeout=10)
|
139
|
-
deadline = Time.now + timeout
|
140
|
-
until Time.now >= deadline
|
141
|
-
result = yield
|
142
|
-
if result
|
143
|
-
return
|
144
|
-
else
|
145
|
-
sleep 0.1
|
146
|
-
end
|
147
|
-
end
|
148
|
-
raise "Timeout expired, running now: #{find_worker_pids.inspect}"
|
149
|
-
end
|
150
|
-
|
151
|
-
def all_are_different?(one, two)
|
152
|
-
(one & two).empty?
|
153
|
-
end
|
154
|
-
|
155
|
-
end
|
data/test/test_daemon.rb
DELETED