forkme 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|