daemonic 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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