forkr 0.1.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/forkr.gemspec +2 -2
  3. data/lib/multi_forkr.rb +163 -0
  4. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f0539b882a7e449399d84d53b5f84514db12af81
4
- data.tar.gz: 41f718fe40fb072bd4c5e6f0e28953ac97f0d917
3
+ metadata.gz: 3def66e8f139ed116bb7beaf96207eea08e0dab7
4
+ data.tar.gz: 9f34069eaafcf424c414803a184b4cf7aa8b9554
5
5
  SHA512:
6
- metadata.gz: 6c4f42032dee53cbfd4898dc0bdc9fae67146afdd68fe132c6d5338f4a9b21115057b8b981b70fde957a033266d66b471f2f06dd3313471c42c3de94c26802b6
7
- data.tar.gz: bc1dd886ec97c7129f362bc56b2c9cd4fa4d98a5c6a45c7f93e1d74c2e7ebc8dfa10221380cd5bff34cf0f12c6fd7449be0bb16b307d63482b8048e08388e8fe
6
+ metadata.gz: 0cc9016ae45bb4e64c586a316ee13bed3799c1aa6798fc90af5566ef1800c8d8e62340c8cc7384bc60bfe5d4122cb0a2fa02eb2883b9bd081293c7192de5d6a2
7
+ data.tar.gz: 302d7d9c68e3c82404992ea49bb874e6b934fe02177481f838b13729fc285d6611c1bbf248199c5396ffa9a5e0a212f2bc75ee772b33556f06328ddf1bb71db0
data/forkr.gemspec CHANGED
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'forkr'
3
- s.version = '0.1.7'
3
+ s.version = '1.0.0'
4
4
  s.date = '2015-03-22'
5
5
  s.summary = "A pre-forking worker host - shamelessly inspired by unicorn."
6
6
  s.description = "Forkr is a preforking worker host, shamelessly inspired by unicorn. It exists to easily fork and scale ruby programs which aren't rack-based."
7
7
  s.authors = ["Trey Evans"]
8
8
  s.email = 'lewis.r.evans@gmail.com'
9
- s.files = ["lib/forkr.rb", "forkr.gemspec", "README.md"]
9
+ s.files = ["lib/forkr.rb", "lib/multi_forkr.rb", "forkr.gemspec", "README.md"]
10
10
  s.homepage = 'https://github.com/TreyE/forkr'
11
11
  s.license = 'MIT'
12
12
  end
@@ -0,0 +1,163 @@
1
+ class MultiForkr
2
+
3
+ # The PID of the Forkr master
4
+ # @return [Fixnum]
5
+ attr_reader :master_pid
6
+
7
+ # Child Process Definitions
8
+ # @return Hash<Object, Integer>
9
+ attr_reader :child_defs
10
+
11
+ # Child process pids.
12
+ # @return Hash<Object, [Pids]>
13
+ attr_reader :child_sets
14
+
15
+ # @param forklets Hash<Object, Integer> the worker objects, with counts
16
+ def initialize(forklets)
17
+ @child_defs = forklets
18
+ @master_pid = $$
19
+ @child_sets = Hash.new { |h, k| h[k] = Array.new }
20
+ @in_shutdown = false
21
+ end
22
+
23
+ # Start the master, and spawn workers
24
+ # @return [nil]
25
+ def run
26
+ @inbound, @outbound = IO.pipe
27
+ Signal.trap('CHLD') { dead_child }
28
+ Signal.trap('INT') { interrupt }
29
+ Signal.trap('TERM') { shutdown }
30
+ Signal.trap('QUIT') { core_dump_quit }
31
+ master_loop
32
+ end
33
+
34
+ protected
35
+
36
+ attr_reader :inbound, :outbound
37
+
38
+ def send_wake_notice(notice)
39
+ return(nil) if $$ != master_pid
40
+ return(nil) if @in_shutdown
41
+ @outbound.write(notice)
42
+ end
43
+
44
+ def core_dump_quit
45
+ send_wake_notice("Q")
46
+ end
47
+
48
+ def interrupt
49
+ send_wake_notice("I")
50
+ end
51
+
52
+ def shutdown
53
+ send_wake_notice("T")
54
+ end
55
+
56
+ def dead_child
57
+ send_wake_notice("D")
58
+ end
59
+
60
+ def spawn_worker(forklet)
61
+ if new_pid = fork
62
+ existing_worker_pids = @child_sets[forklet]
63
+ @child_sets[forklet] = existing_worker_pids + [new_pid]
64
+ else
65
+ worker_loop(forklet)
66
+ end
67
+ end
68
+
69
+ def shutdown_using(sig)
70
+ @in_shutdown = true
71
+ signal_all_workers(sig)
72
+ raise StopIteration.new
73
+ end
74
+
75
+ def master_loop
76
+ catch(:bail_because_im_a_worker) do
77
+ ensure_right_worker_count
78
+ loop do
79
+ fds = IO.select([@inbound],nil,nil,2)
80
+ unless fds.nil?
81
+ data_read = fds.first.first.read(1)
82
+ if data_read == "I"
83
+ shutdown_using(:INT)
84
+ elsif data_read == "T"
85
+ shutdown_using(:TERM)
86
+ elsif data_read == "Q"
87
+ shutdown_using(:QUIT)
88
+ end
89
+ end
90
+ prune_workers
91
+ ensure_right_worker_count
92
+ end
93
+ reap_all_workers
94
+ @outbound.close
95
+ @inbound.close
96
+ end
97
+ end
98
+
99
+ def reap_all_workers
100
+ begin
101
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
102
+ rescue Errno::ECHILD
103
+ break
104
+ end while true
105
+ end
106
+
107
+ def ensure_right_worker_count
108
+ @child_defs.each_pair do |k, v|
109
+ existing_workers = @child_sets[k]
110
+ off_by = v - existing_workers.length
111
+ if off_by > 0
112
+ off_by.times do
113
+ spawn_worker(k)
114
+ end
115
+ elsif off_by < 0
116
+ existing_workers.take(off_by.abs).each do |kid|
117
+ signal_worker(kid, :TERM)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ def children
124
+ @child_sets.values.map { |v| v }
125
+ end
126
+
127
+ def signal_all_workers(sig)
128
+ @children.each { |c| signal_worker(c, sig) }
129
+ end
130
+
131
+ def signal_worker(wpid, signal)
132
+ begin
133
+ Process.kill(signal, wpid)
134
+ rescue Errno::ESRCH
135
+ end
136
+ end
137
+
138
+ def prune_workers
139
+ new_sets = {}
140
+ @child_sets.each_pair do |k, v|
141
+ living_children = v.reject { |pid| child_dead?(pid) }
142
+ new_sets[k] = living_children
143
+ end
144
+ @child_sets = new_sets
145
+ end
146
+
147
+ def worker_loop(forklet)
148
+ forklet.after_fork if forklet.respond_to?(:after_fork)
149
+ @inbound.close
150
+ @outbound.close
151
+ $stderr.puts "Worker spawned as #{$$}!"
152
+ forklet.run
153
+ throw(:bail_because_im_a_worker)
154
+ end
155
+
156
+ def child_dead?(pid)
157
+ status = Process.waitpid(pid, Process::WNOHANG)
158
+ unless status.nil?
159
+ $stderr.puts "Process #{pid} dead: #{status}"
160
+ end
161
+ !status.nil?
162
+ end
163
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forkr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trey Evans
@@ -20,6 +20,7 @@ files:
20
20
  - README.md
21
21
  - forkr.gemspec
22
22
  - lib/forkr.rb
23
+ - lib/multi_forkr.rb
23
24
  homepage: https://github.com/TreyE/forkr
24
25
  licenses:
25
26
  - MIT