preforker 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +41 -13
- data/VERSION +1 -1
- data/examples/em_echo_server.rb +59 -0
- data/examples/ping_pong.rb +9 -3
- data/lib/preforker.rb +20 -15
- data/preforker.gemspec +4 -2
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -1,43 +1,67 @@
|
|
1
1
|
= preforker
|
2
2
|
|
3
|
-
A gem to easily create prefork servers.
|
3
|
+
A gem to easily create protocol agnostic prefork servers.
|
4
4
|
|
5
5
|
== Example
|
6
6
|
|
7
7
|
Let's see an example using the Mac 'say' command.
|
8
8
|
|
9
|
+
require 'rubygems'
|
9
10
|
require 'preforker'
|
10
11
|
|
11
|
-
#
|
12
|
+
#At this point we can open some socket or reserve any other resource you want to share with your workers.
|
13
|
+
#Whatever we do before Preforker.new is ran only in the master process.
|
14
|
+
say `hi, I\\'m the master`
|
15
|
+
|
12
16
|
Preforker.new(:timeout => 10) do |master|
|
13
|
-
#this block is only run
|
17
|
+
#this block is only run by each worker (10 by default)
|
14
18
|
|
15
|
-
#
|
16
|
-
|
19
|
+
#first you should write the code that is needed to be ran
|
20
|
+
#once when a new fork is created, initializations, etc.
|
21
|
+
`say hi, I\\'m a worker`
|
17
22
|
|
18
|
-
#
|
19
|
-
#you need to ask master if it wants you alive periodically or
|
23
|
+
#now you could IO.select a socket, run an EventMachine service (see example), or as we do here,
|
24
|
+
#just run a worker loop. Notice that you need to ask master if it wants you alive periodically or
|
25
|
+
#else it will kill you after the timeout elapses. Respect your master!
|
20
26
|
while master.wants_me_alive? do
|
21
27
|
sleep 1
|
22
28
|
`say ping pong`
|
23
29
|
end
|
24
30
|
|
25
|
-
#here we can do whatever we want
|
31
|
+
#here we can do whatever we want to exit gracefully
|
26
32
|
`say bye`
|
33
|
+
|
34
|
+
|
35
|
+
#here we are using #start to daemonize. We could have used #run to just block and then send the INT signal with ctrl-c to stop
|
27
36
|
end.start
|
28
37
|
|
29
|
-
|
38
|
+
puts "I'm the launcher that forked off master, bye bye"
|
39
|
+
puts "To kill the server just do: kill -s QUIT `cat preforker.pid`
|
40
|
+
|
41
|
+
Remember that to kill the server you need to:
|
30
42
|
|
31
43
|
kill -s QUIT `cat preforker.pid`
|
32
44
|
|
33
45
|
See the examples directory and the specs for more examples.
|
34
46
|
|
47
|
+
== Why? I can always use threads!
|
48
|
+
|
49
|
+
As always, it's a matter of trade offs, so it depends on how you ponder the advantages and disadvantages of a preforking architecture for your particular case.
|
50
|
+
Still notice that you could have a mix of threads and processes, they are independent concepts.
|
51
|
+
|
52
|
+
=== Advantages
|
53
|
+
[Reliability] You may be using a third party library or a C extension with memory leaks or segfaults that could break your entire ruby process. If you use a preforking architecture you can just kill the misbehaving process without affecting the rest of your healthy workers.
|
54
|
+
[Simplicity] When creating servers you can reduce the amount of extra code (thread pools, triggering, callbacks, etc) needed to deal with concurrency. Still you should always be aware that your app will be concurrent and correctly deal with shared resources, locking and possible race conditions. The concurrency aspect is very decoupled from your app (although in a coarse grained way) so in some cases you can add concurrency to a legacy library without changing its code, just by adding processes.
|
55
|
+
|
56
|
+
=== Disadvantages
|
57
|
+
[Efficiency] Threads are more fine grained so you have more control over which parts of your app should be concurrent. Efficiency is also better because context switching is cheap in threaded systems. But note that AFAIK this is still not the case in Ruby, specially if not using REE, see {here}[http://timetobleed.com/ruby-hoedown-slides/].
|
58
|
+
|
35
59
|
== Configuration options
|
36
60
|
|
37
61
|
[:timeout] The timeout in seconds, 5 by default. If a worker takes more than this it will be killed and respawned.
|
38
62
|
[:workers] Number of workers, 10 by default.
|
39
|
-
[:stdout_path] Path to redirect stdout to. By default it's /dev/null
|
40
|
-
[:stderr_path] Path to redirect stderr to. By default it's /dev/null
|
63
|
+
[:stdout_path] Path to redirect stdout to. By default it's the log file. You may prefer to use /dev/null or /dev/stdout
|
64
|
+
[:stderr_path] Path to redirect stderr to. By default it's the log file. You may prefer to use /dev/null or /dev/stderr
|
41
65
|
[:app_name] The app name, 'preforker' by default. Used for some ps message, log messages messages and pid file name.
|
42
66
|
[:pid_path] The path to the pid file for this server. By default it's './preforker.pid'.
|
43
67
|
[:logger] This is Logger.new('./preforker.log') by default
|
@@ -52,15 +76,19 @@ You can send some signals to master to control the way it handles the workers li
|
|
52
76
|
[QUIT] Kill workers and master in a graceful way
|
53
77
|
[TERM, INT] Kill workers and master immediately
|
54
78
|
|
79
|
+
== Installation
|
80
|
+
|
81
|
+
gem install preforker
|
82
|
+
|
55
83
|
== Acknowledgments
|
56
84
|
|
57
|
-
Most of the preforking operating system tricks come from Unicorn.
|
85
|
+
Most of the preforking operating system tricks come from {Unicorn}[http://unicorn.bogomips.org/]. I checked out its source code and read {this}[http://tomayko.com/writings/unicorn-is-unix] great introduction by {rtomayko}[http://github.com/rtomayko].
|
58
86
|
|
59
87
|
== To do list
|
60
88
|
|
61
89
|
* More tests
|
62
90
|
* Log rotation through the USR1 signal
|
63
|
-
* Have something like min_spare_workers, max_workers
|
91
|
+
* Have something like min_spare_workers, max_workers, max_request_per_worker
|
64
92
|
|
65
93
|
== Note on Patches/Pull Requests
|
66
94
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'preforker'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
class EchoServer < EM::Connection
|
6
|
+
def notify_readable
|
7
|
+
while socket = @io.accept_nonblock
|
8
|
+
message = socket.gets
|
9
|
+
socket.write message
|
10
|
+
#let's fake some more work is done
|
11
|
+
sleep 0.3
|
12
|
+
socket.close
|
13
|
+
end
|
14
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
15
|
+
end
|
16
|
+
|
17
|
+
def unbind
|
18
|
+
detach
|
19
|
+
@io.close
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
socket = TCPServer.new("0.0.0.0", 8081)
|
24
|
+
socket.listen(100)
|
25
|
+
EventMachine.epoll
|
26
|
+
|
27
|
+
Preforker.new(:timeout => 5, :workers => 4, :app_name => "EM example") do |master|
|
28
|
+
EventMachine::run do
|
29
|
+
EM.add_periodic_timer(4) do
|
30
|
+
EM.stop_event_loop unless master.wants_me_alive?
|
31
|
+
end
|
32
|
+
|
33
|
+
EM.watch(socket, EchoServer, self){ |c| c.notify_readable = true}
|
34
|
+
puts "Listening..."
|
35
|
+
end
|
36
|
+
end.start
|
37
|
+
|
38
|
+
__END__
|
39
|
+
ab -c 4 -n 100 "http://127.0.0.1:8081/"
|
40
|
+
|
41
|
+
Concurrency Level: 4
|
42
|
+
Time taken for tests: 8.202 seconds
|
43
|
+
Complete requests: 100
|
44
|
+
|
45
|
+
|
46
|
+
##################################
|
47
|
+
Compare it with this other implementation that doesn't use preforker (or EM.defer)
|
48
|
+
|
49
|
+
EventMachine::run do
|
50
|
+
EM.watch(socket, EchoServer, self){ |c| c.notify_readable = true}
|
51
|
+
puts "Listening..."
|
52
|
+
end
|
53
|
+
|
54
|
+
ab -c 4 -n 100 "http://127.0.0.1:8081/"
|
55
|
+
|
56
|
+
Concurrency Level: 4
|
57
|
+
Time taken for tests: 29.729 seconds
|
58
|
+
Complete requests: 100
|
59
|
+
|
data/examples/ping_pong.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
1
2
|
require 'preforker'
|
2
3
|
|
3
4
|
#you can open some socket here or reserve any other resource you want to share with your workers, this is master space
|
5
|
+
`say hi, I\\'m the master`
|
6
|
+
|
4
7
|
Preforker.new(:timeout => 10) do |master|
|
5
8
|
#this block is only run from each worker (10 by default)
|
6
9
|
|
7
|
-
#here you should write the code that is needed to be ran each time a fork is created, initializations, etc.
|
8
|
-
`say hi`
|
10
|
+
#here you should write the code that is needed to be ran once each time a fork is created, initializations, etc.
|
11
|
+
`say hi, I\\'m a worker`
|
9
12
|
|
10
13
|
#here you could IO.select a socket, run an EventMachine service (see example), or just run worker loop
|
11
14
|
#you need to ask master if it wants you alive periodically or else it will kill you after the timeout elapses. Respect your master!
|
@@ -16,6 +19,9 @@ Preforker.new(:timeout => 10) do |master|
|
|
16
19
|
|
17
20
|
#here we can do whatever we want when exiting gracefully
|
18
21
|
`say bye`
|
22
|
+
|
23
|
+
#we can use run instead of start to run the server without daemonizing
|
19
24
|
end.start
|
20
25
|
|
21
|
-
|
26
|
+
puts "I'm the launching process that forked to create master, I did my job. Enjoy the noisy ping pong championship, bye bye!"
|
27
|
+
puts "To kill the server just do: kill -s QUIT `cat preforker.pid`
|
data/lib/preforker.rb
CHANGED
@@ -30,27 +30,33 @@ class Preforker
|
|
30
30
|
$0 = "#@app_name Master"
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
set_stderr_path(@options[:stderr_path])
|
33
|
+
def run(ready_write = nil)
|
34
|
+
$stdin.reopen("/dev/null")
|
35
|
+
set_stdout_path(@options[:stdout_path])
|
36
|
+
set_stderr_path(@options[:stderr_path])
|
38
37
|
|
39
|
-
|
38
|
+
logger.info "Master started"
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
pid_path = @options[:pid_path] || "./#{@app_name.downcase}.pid"
|
41
|
+
@pid_manager = PidManager.new(pid_path)
|
42
|
+
@signal_processor = SignalProcessor.new(self)
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
spawn_missing_workers do
|
45
|
+
ready_write.close if ready_write
|
46
|
+
end
|
48
47
|
|
49
|
-
|
48
|
+
#tell parent we are ready
|
49
|
+
if ready_write
|
50
50
|
ready_write.syswrite($$.to_s)
|
51
51
|
ready_write.close rescue nil
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
+
@signal_processor.start_signal_loop
|
55
|
+
end
|
56
|
+
|
57
|
+
def start
|
58
|
+
launch do |ready_write|
|
59
|
+
run(ready_write)
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
@@ -77,7 +83,6 @@ class Preforker
|
|
77
83
|
exit!(1)
|
78
84
|
else
|
79
85
|
puts "Server started successfuly"
|
80
|
-
exit(0)
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
data/preforker.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{preforker}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Daniel Cadenas"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-11}
|
13
13
|
s.description = %q{A gem to easily create prefork servers.}
|
14
14
|
s.email = %q{dcadenas@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
"VERSION",
|
26
26
|
"examples/amqp.rb",
|
27
27
|
"examples/amqp_client.rb",
|
28
|
+
"examples/em_echo_server.rb",
|
28
29
|
"examples/ping_pong.rb",
|
29
30
|
"lib/preforker.rb",
|
30
31
|
"lib/preforker/pid_manager.rb",
|
@@ -52,6 +53,7 @@ Gem::Specification.new do |s|
|
|
52
53
|
"spec/support/integration.rb",
|
53
54
|
"examples/amqp.rb",
|
54
55
|
"examples/amqp_client.rb",
|
56
|
+
"examples/em_echo_server.rb",
|
55
57
|
"examples/ping_pong.rb"
|
56
58
|
]
|
57
59
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Daniel Cadenas
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-11 00:00:00 -03:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- VERSION
|
64
64
|
- examples/amqp.rb
|
65
65
|
- examples/amqp_client.rb
|
66
|
+
- examples/em_echo_server.rb
|
66
67
|
- examples/ping_pong.rb
|
67
68
|
- lib/preforker.rb
|
68
69
|
- lib/preforker/pid_manager.rb
|
@@ -114,4 +115,5 @@ test_files:
|
|
114
115
|
- spec/support/integration.rb
|
115
116
|
- examples/amqp.rb
|
116
117
|
- examples/amqp_client.rb
|
118
|
+
- examples/em_echo_server.rb
|
117
119
|
- examples/ping_pong.rb
|