preforker 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- #you can open some socket here or reserve any other resource you want to share with your workers, this is master space
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 from each worker (10 by default)
17
+ #this block is only run by each worker (10 by default)
14
18
 
15
- #here you should write the code that is needed to be ran each time a fork is created, initializations, etc.
16
- `say hi`
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
- #here you could IO.select a socket, run an EventMachine service (see example), or just run worker loop
19
- #you need to ask master if it wants you alive periodically or else it will kill you after the timeout elapses. Respect your master!
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 when exiting gracefully
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
- To kill the server just run:
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. Check out its source code for a hands on tutorial on Unix internals!
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.0
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
+
@@ -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
- #to kill the server just run: kill -s QUIT `cat preforker.pid`
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`
@@ -30,27 +30,33 @@ class Preforker
30
30
  $0 = "#@app_name Master"
31
31
  end
32
32
 
33
- def start
34
- launch do |ready_write|
35
- $stdin.reopen("/dev/null")
36
- set_stdout_path(@options[:stdout_path])
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
- logger.info "Master started"
38
+ logger.info "Master started"
40
39
 
41
- pid_path = @options[:pid_path] || "./#{@app_name.downcase}.pid"
42
- @pid_manager = PidManager.new(pid_path)
43
- @signal_processor = SignalProcessor.new(self)
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
- spawn_missing_workers do
46
- ready_write.close
47
- end
44
+ spawn_missing_workers do
45
+ ready_write.close if ready_write
46
+ end
48
47
 
49
- #tell parent we are ready
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
- @signal_processor.start_signal_loop
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
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{preforker}
8
- s.version = "0.1.0"
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-02}
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
- - 0
9
- version: 0.1.0
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-02 00:00:00 -03:00
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