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.
@@ -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