forkr 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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/forkr.rb +155 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 76c883a35bda24bd878cfa72a2a45f506d67cd2c
4
+ data.tar.gz: e378330ad4969a24d3554216a0a7a0521aa0df0e
5
+ SHA512:
6
+ metadata.gz: bf8932667a70eb7b31b6d0ce367f17faa166267d77c0a49eb3317b25e33454d8439c6edcb97f1df0dbc6e35f729a92fc364713f4067977244127b53e6348c5a1
7
+ data.tar.gz: 5da12ece3a39c67f62e41295a9226cb736c33d666bf79d081e6d02e4b7fe77e87c7b9c8d7823706ede81a604250bb2e7c3dc2878cef25f704c2aa5defaed9135
data/lib/forkr.rb ADDED
@@ -0,0 +1,155 @@
1
+ class Forkr
2
+ attr_reader :master_pid, :children, :inbound, :outbound, :child_count
3
+
4
+ def initialize(forklet, num_kids = 1)
5
+ @worker_client = forklet
6
+ @master_pid = $$
7
+ @children = []
8
+ @child_count = num_kids
9
+ @in_shutdown = false
10
+ end
11
+
12
+ def run
13
+ @inbound, @outbound = IO.pipe
14
+ Signal.trap('CHLD') { dead_child }
15
+ Signal.trap('INT') { interrupt }
16
+ Signal.trap('TERM') { shutdown }
17
+ Signal.trap('QUIT') { core_dump_quit }
18
+ Signal.trap('TTIN') { add_worker }
19
+ Signal.trap('TTOU') { remove_worker }
20
+ master_loop
21
+ end
22
+
23
+ def send_wake_notice(notice)
24
+ return(nil) if $$ != master_pid
25
+ return(nil) if @in_shutdown
26
+ @outbound.write(notice)
27
+ end
28
+
29
+ def core_dump_quit
30
+ send_wake_notice("Q")
31
+ end
32
+
33
+ def add_worker
34
+ send_wake_notice("+")
35
+ end
36
+
37
+ def remove_worker
38
+ send_wake_notice("-")
39
+ end
40
+
41
+ def interrupt
42
+ send_wake_notice("I")
43
+ end
44
+
45
+ def shutdown
46
+ send_wake_notice("T")
47
+ end
48
+
49
+ def dead_child
50
+ send_wake_notice("D")
51
+ end
52
+
53
+ def spawn_worker
54
+ if new_pid = fork
55
+ @children << new_pid
56
+ else
57
+ worker_loop
58
+ end
59
+ end
60
+
61
+ def increment_workers
62
+ @child_count = @child_count + 1
63
+ end
64
+
65
+ def decrement_workers
66
+ if @child_count > 1
67
+ @child_count = @child_count - 1
68
+ end
69
+ end
70
+
71
+ def shutdown_using(sig)
72
+ @in_shutdown = true
73
+ signal_all_workers(sig)
74
+ raise StopIteration.new
75
+ end
76
+
77
+ def master_loop
78
+ ensure_right_worker_count
79
+ loop do
80
+ fds = IO.select([@inbound],nil,nil,2)
81
+ unless fds.nil?
82
+ data_read = fds.first.first.read(1)
83
+ if data_read == "I"
84
+ shutdown_using(:INT)
85
+ elsif data_read == "T"
86
+ shutdown_using(:TERM)
87
+ elsif data_read == "Q"
88
+ shutdown_using(:QUIT)
89
+ elsif data_read == "+"
90
+ increment_workers
91
+ elsif data_read == "-"
92
+ decrement_workers
93
+ end
94
+ end
95
+ prune_workers
96
+ ensure_right_worker_count
97
+ end
98
+ reap_all_workers
99
+ @outbound.close
100
+ @inbound.close
101
+ end
102
+
103
+ def reap_all_workers
104
+ begin
105
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
106
+ rescue Errno::ECHILD
107
+ break
108
+ end while true
109
+ end
110
+
111
+ def ensure_right_worker_count
112
+ existing_workers = @children.length
113
+ off_by = @child_count - @children.length
114
+ if off_by > 0
115
+ off_by.times do
116
+ spawn_worker
117
+ end
118
+ elsif off_by < 0
119
+ @children.take(off_by.abs).each do |kid|
120
+ signal_worker(kid, :TERM)
121
+ end
122
+ end
123
+ end
124
+
125
+ def signal_all_workers(sig)
126
+ @children.each { |c| signal_worker(c, sig) }
127
+ end
128
+
129
+ def signal_worker(wpid, signal)
130
+ begin
131
+ Process.kill(signal, wpid)
132
+ rescue Errno::ESRCH
133
+ end
134
+ end
135
+
136
+ def prune_workers
137
+ @children = @children.reject { |pid| child_dead?(pid) }
138
+ end
139
+
140
+ def worker_loop
141
+ @worker_client.after_fork if @worker_client.respond_to?(:after_fork)
142
+ @inbound.close
143
+ @outbound.close
144
+ $stderr.puts "Worker spawned as #{$$}!"
145
+ @worker_client.run
146
+ end
147
+
148
+ def child_dead?(pid)
149
+ status = Process.waitpid(pid, Process::WNOHANG)
150
+ unless status.nil?
151
+ $stderr.puts "Process #{pid} dead: #{status}"
152
+ end
153
+ !status.nil?
154
+ end
155
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forkr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Trey Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A pre-forking worker host.
14
+ email: lewis.r.evans@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/forkr.rb
20
+ homepage: http://rubygems.org/gems/hola
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.2.2
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: A pre-forking worker host - shamelessly inspired by unicorn.
44
+ test_files: []