daemonic 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -1,6 +0,0 @@
1
- --workers 5
2
- --pid tmp/test.pid
3
- --name daemon_test
4
- --command "ruby test/test_daemon.rb"
5
- --loglevel debug
6
- --log log/test.log
@@ -1 +0,0 @@
1
- exit 1
@@ -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
@@ -1,10 +0,0 @@
1
- exiting = false
2
- trap("TERM") { exiting = true }
3
- trap("INT") { exiting = true }
4
- trap("HUP") { puts "HUP HOLLAND HUP" }
5
-
6
- $PROGRAM_NAME = "test_daemon.rb daemon_test worker##{ENV["DAEMON_WORKER_NUMBER"]}"
7
-
8
- until exiting
9
- sleep 0.1
10
- end