forkme 0.0.6 → 0.0.7
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.
- data/Manifest +4 -0
- data/forkme.gemspec +2 -2
- data/lib/forkme/child.rb +47 -0
- data/lib/forkme/children.rb +40 -0
- data/lib/forkme/default_logger.rb +13 -0
- data/lib/forkme/forkme.rb +182 -0
- metadata +8 -5
data/Manifest
CHANGED
data/forkme.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{forkme}
|
5
|
-
s.version = "0.0.
|
5
|
+
s.version = "0.0.7"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Tracey Eubanks"]
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = %q{Manage a fork pool}
|
11
11
|
s.email = %q{traceye@pmamediagroup.com}
|
12
12
|
s.extra_rdoc_files = ["lib/forkme.rb"]
|
13
|
-
s.files = ["Rakefile", "lib/forkme.rb", "readme", "Manifest", "forkme.gemspec"]
|
13
|
+
s.files = ["Rakefile", "lib/forkme.rb", "readme", "Manifest", "forkme.gemspec", "lib/forkme/child.rb", "lib/forkme/children.rb", "lib/forkme/default_logger.rb", "lib/forkme/forkme.rb"]
|
14
14
|
s.homepage = %q{http://github.com/narshlob/forkme}
|
15
15
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Forkme", "--main", "readme"]
|
16
16
|
s.require_paths = ["lib"]
|
data/lib/forkme/child.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class Child < Forkme
|
2
|
+
def initialize(pid, from, to)
|
3
|
+
@pid, @from, @to = pid, from, to
|
4
|
+
@status = :idle
|
5
|
+
end
|
6
|
+
# status is one of :idle, :connect, :close, :exit
|
7
|
+
|
8
|
+
attr_accessor :pid, :from, :to
|
9
|
+
|
10
|
+
def event(s)
|
11
|
+
if s == nil then
|
12
|
+
#Forkme.logger.debug "p: child #{pid} terminated"
|
13
|
+
self.exit
|
14
|
+
else
|
15
|
+
case s.chomp
|
16
|
+
when "connect" then @status = :connect
|
17
|
+
when "disconnect" then @status = :idle
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
Forkme.logger.error "unknown status: #{s}"
|
21
|
+
rescue NoMethodError
|
22
|
+
end
|
23
|
+
|
24
|
+
return "unknown status: #{s}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def close()
|
30
|
+
@to.close unless @to.closed?
|
31
|
+
@status = :close
|
32
|
+
end
|
33
|
+
|
34
|
+
def exit()
|
35
|
+
@from.close unless @from.closed?
|
36
|
+
@to.close unless @to.closed?
|
37
|
+
@status = :exit
|
38
|
+
end
|
39
|
+
|
40
|
+
def idle?()
|
41
|
+
@status == :idle
|
42
|
+
end
|
43
|
+
|
44
|
+
def active?()
|
45
|
+
@status == :idle or @status == :connect
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Children < Array
|
2
|
+
def fds()
|
3
|
+
self.map{|c| c.active? ? c.from : nil}.compact.flatten
|
4
|
+
end
|
5
|
+
|
6
|
+
def pids()
|
7
|
+
self.map{|c| c.pid}
|
8
|
+
end
|
9
|
+
|
10
|
+
def active()
|
11
|
+
self.map{|c| c.active? ? c : nil}.compact
|
12
|
+
end
|
13
|
+
|
14
|
+
def idle()
|
15
|
+
self.map{|c| c.idle? ? c : nil}.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_fd(fd)
|
19
|
+
self.each do |c|
|
20
|
+
return c if c.from == fd
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def cleanup()
|
26
|
+
new = Children.new
|
27
|
+
self.each do |c|
|
28
|
+
begin
|
29
|
+
if Process.waitpid(c.pid, Process::WNOHANG) then
|
30
|
+
#Forkme.logger.debug "p: catch exited child #{c.pid}"
|
31
|
+
c.exit
|
32
|
+
else
|
33
|
+
new << c
|
34
|
+
end
|
35
|
+
rescue Errno::ECHILD
|
36
|
+
end
|
37
|
+
end
|
38
|
+
self.replace new
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
class Forkme
|
2
|
+
include DefaultLogger
|
3
|
+
attr_reader :child_count
|
4
|
+
attr_accessor :suppress_exceptions
|
5
|
+
attr_writer :on_child_start_blk, :on_child_exit_blk, :max_forks
|
6
|
+
|
7
|
+
# These class methods actually set up the logger that's used
|
8
|
+
# to print out useful information
|
9
|
+
class << self
|
10
|
+
def logger
|
11
|
+
@logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def logger=(log_meth)
|
15
|
+
@logger = log_meth
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.start(forks_to_run, suppress_exceptions = false, log_method = DefaultLogger, &block)
|
20
|
+
self.new(forks_to_run, suppress_exceptions, log_method).start(block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(forks_to_run, suppress_exceptions = false, log_method = DefaultLogger)
|
24
|
+
@min_forks = 1
|
25
|
+
@max_forks = forks_to_run
|
26
|
+
@suppress_exceptions = suppress_exceptions
|
27
|
+
Forkme.logger = log_method
|
28
|
+
end
|
29
|
+
|
30
|
+
# class variable holding all the children
|
31
|
+
@@children = Children.new
|
32
|
+
|
33
|
+
# A block to be executed just before calling the block a child
|
34
|
+
# will be executing
|
35
|
+
#
|
36
|
+
# [block] block The block that will be executed
|
37
|
+
def on_child_start(&block)
|
38
|
+
raise "block required" unless block_given?
|
39
|
+
@on_child_start_blk = block
|
40
|
+
end
|
41
|
+
|
42
|
+
# A block to be executed upon exiting a child process.
|
43
|
+
#
|
44
|
+
# [block] block The block that will be executed upon termination of a child process
|
45
|
+
def on_child_exit(&block)
|
46
|
+
raise "block required" unless block_given?
|
47
|
+
@on_child_exit_blk = block
|
48
|
+
end
|
49
|
+
|
50
|
+
# Starts the child processes, the number of which is determined by the @max_forks variable
|
51
|
+
#
|
52
|
+
# [block] &block The block that will be executed by the child processes
|
53
|
+
def start(&block)
|
54
|
+
raise "block required" unless block_given?
|
55
|
+
|
56
|
+
(@min_forks - @@children.size).times do
|
57
|
+
make_child block
|
58
|
+
end
|
59
|
+
@flag = :in_loop
|
60
|
+
while @flag == :in_loop do
|
61
|
+
log = false
|
62
|
+
r, = IO.select(@@children.fds, nil, nil, 1)
|
63
|
+
if r then
|
64
|
+
log = true
|
65
|
+
r.each do |f|
|
66
|
+
c = @@children.by_fd f
|
67
|
+
c.event f.gets
|
68
|
+
end
|
69
|
+
end
|
70
|
+
as = @@children.active.size
|
71
|
+
@@children.cleanup if @@children.size > as
|
72
|
+
break if @flag != :in_loop
|
73
|
+
n = 0
|
74
|
+
if as < @min_forks then
|
75
|
+
n = @min_forks - as
|
76
|
+
else
|
77
|
+
if @@children.idle.size <= 2 then
|
78
|
+
n = 2
|
79
|
+
end
|
80
|
+
end
|
81
|
+
if as + n > @max_forks then
|
82
|
+
n = @max_forks - as
|
83
|
+
end
|
84
|
+
#Forkme.logger.debug "p: max:#{@max_forks}, min:#{@min_forks}, cur:#{as}, idle:#{@@children.idle.size}: new:#{n}" if n > 0 or log
|
85
|
+
n.times do
|
86
|
+
make_child block
|
87
|
+
end
|
88
|
+
end
|
89
|
+
@flag = :out_of_loop
|
90
|
+
terminate
|
91
|
+
end
|
92
|
+
|
93
|
+
# sets the @flag to :exit_loop, essentially stopping the parent and child processes
|
94
|
+
# since the loop will die and all children will receive the close() call
|
95
|
+
def stop()
|
96
|
+
@flag = :exit_loop
|
97
|
+
terminate
|
98
|
+
end
|
99
|
+
|
100
|
+
# Calls the close method on each child which sets its status to :closed
|
101
|
+
def terminate()
|
102
|
+
raise "Cannot terminate while still in the loop" if @flag == :in_loop
|
103
|
+
@@children.each do |c|
|
104
|
+
c.close
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sends the TERM signal to all child processes via their pids
|
109
|
+
def interrupt()
|
110
|
+
Process.kill "TERM", *(@@children.pids) rescue nil
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# called by the child process when it's finished the block passed to it
|
116
|
+
def exit_child
|
117
|
+
exit!
|
118
|
+
end
|
119
|
+
|
120
|
+
# Creates a child process and tells that process to execute a block
|
121
|
+
# It also sets up the to and from pipes to be shared between the
|
122
|
+
# parent and child processes.
|
123
|
+
#
|
124
|
+
# [block] block The block to be executed by the child
|
125
|
+
def make_child(block)
|
126
|
+
#Forkme.logger.debug "p: make child"
|
127
|
+
to_child = IO.pipe
|
128
|
+
to_parent = IO.pipe
|
129
|
+
pid = fork do
|
130
|
+
@@children.map do |c|
|
131
|
+
c.from.close unless c.from.closed?
|
132
|
+
c.to.close unless c.to.closed?
|
133
|
+
end
|
134
|
+
@from_parent = to_child[0]
|
135
|
+
@to_parent = to_parent[1]
|
136
|
+
to_child[1].close
|
137
|
+
to_parent[0].close
|
138
|
+
|
139
|
+
child block
|
140
|
+
exit_child
|
141
|
+
end
|
142
|
+
#Forkme.logger.debug "p: child pid #{pid}"
|
143
|
+
@@children << Child.new(pid, to_parent[0], to_child[1])
|
144
|
+
to_child[0].close
|
145
|
+
to_parent[1].close
|
146
|
+
end
|
147
|
+
|
148
|
+
# Method to call the block that's been passed to it
|
149
|
+
#
|
150
|
+
# [block] the block that will be called within the child process
|
151
|
+
def child(block)
|
152
|
+
#Forkme.logger.debug "c: start"
|
153
|
+
# Handle these different signals the child might encounter
|
154
|
+
# This signal trapping actually does get handled within the child
|
155
|
+
# since it's called from within the fork method
|
156
|
+
handle_signals(["TERM", "INT", "HUP"])
|
157
|
+
#Forkme.logger.debug "c: connect from client"
|
158
|
+
@to_parent.syswrite "connect\n"
|
159
|
+
@on_child_start_blk.call if(defined?(@on_child_start_blk))
|
160
|
+
begin
|
161
|
+
block.call
|
162
|
+
rescue => e
|
163
|
+
unless(@suppress_exceptions)
|
164
|
+
Forkme.logger.error e.message
|
165
|
+
Forkme.logger.error e.backtrace.join("\n")
|
166
|
+
raise e
|
167
|
+
end
|
168
|
+
end
|
169
|
+
#Forkme.logger.debug "c: disconnect from client"
|
170
|
+
@to_parent.syswrite "disconnect\n" rescue nil
|
171
|
+
@on_child_exit_blk.call if(defined?(@on_child_exit_blk))
|
172
|
+
end
|
173
|
+
|
174
|
+
# Creates signal traps for the array of signals passed to it
|
175
|
+
#
|
176
|
+
# [Array] sigs The signals that will be trapped
|
177
|
+
def handle_signals(sigs)
|
178
|
+
sigs.each do |signal|
|
179
|
+
trap(signal) { exit_child }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forkme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 0.0.6
|
4
|
+
version: 0.0.7
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Tracey Eubanks
|
@@ -28,6 +27,10 @@ files:
|
|
28
27
|
- readme
|
29
28
|
- Manifest
|
30
29
|
- forkme.gemspec
|
30
|
+
- lib/forkme/child.rb
|
31
|
+
- lib/forkme/children.rb
|
32
|
+
- lib/forkme/default_logger.rb
|
33
|
+
- lib/forkme/forkme.rb
|
31
34
|
has_rdoc: true
|
32
35
|
homepage: http://github.com/narshlob/forkme
|
33
36
|
licenses: []
|
@@ -43,21 +46,21 @@ rdoc_options:
|
|
43
46
|
require_paths:
|
44
47
|
- lib
|
45
48
|
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
-
none: false
|
47
49
|
requirements:
|
48
50
|
- - ">="
|
49
51
|
- !ruby/object:Gem::Version
|
50
52
|
version: "0"
|
53
|
+
version:
|
51
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
55
|
requirements:
|
54
56
|
- - ">="
|
55
57
|
- !ruby/object:Gem::Version
|
56
58
|
version: "1.2"
|
59
|
+
version:
|
57
60
|
requirements: []
|
58
61
|
|
59
62
|
rubyforge_project: forkme
|
60
|
-
rubygems_version: 1.5
|
63
|
+
rubygems_version: 1.3.5
|
61
64
|
signing_key:
|
62
65
|
specification_version: 3
|
63
66
|
summary: Manage a fork pool
|