prefork_engine 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 227fa879a618645e19ba65f3abfc259f69f5dc79
4
- data.tar.gz: 416e99bead194dec4626ce28a7bcb0db3a5f9b01
3
+ metadata.gz: 28b3d832f86df0ffd4cc4f50995409fdf6e5fa03
4
+ data.tar.gz: 7cccd2f8bf896a14e5695616cf745b86c0905c10
5
5
  SHA512:
6
- metadata.gz: 9c7b50e3ebe7fed72b1c390c77ec102fb19aa36ad5350361a92f36b9fbeaa242d51c799f0674e93811b500f598fc2e16e04b557f45b78b3af8c7d9b6641f5041
7
- data.tar.gz: adf4233d2a8d8b164d1dafdb160e994855edb56721096bda279dd423cf461b87c97ffe2ba9f597c808724d62f2fb10aecb7b62834a019542c0b3fa83af08ed92
6
+ metadata.gz: 0ca812d9907a61cfe97d6199d3c3f72c56b4e92ef82a34b1748933cb10affecac50081d1cf0e1235e9a173323f3bebae218e6c7150661bfd9cd1b08bfa2ca1a9
7
+ data.tar.gz: 772d6caa397e481338a53018fe948f83172cffbf52bcc057190e4d963acf427a9a3498d040e54daff91c9b7a8b1cb4858bca24cedae3c3e40027f58586e89b4a
data/Changes ADDED
@@ -0,0 +1,4 @@
1
+ 0.0.2 2014-12-11T13:22:49Z
2
+
3
+ - remove unnecessary gems from requires
4
+ - add example rack handler
@@ -0,0 +1,13 @@
1
+ class HelloApp
2
+ def call(env)
3
+ # p env
4
+ [
5
+ 200,
6
+ { 'Content-Type' => 'text/html' },
7
+ ['hello world ']
8
+ ]
9
+ end
10
+ end
11
+
12
+ run HelloApp.new
13
+
@@ -0,0 +1,306 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # sample Rack handler using PreforkEngine
4
+ # This is ruby port of Starlet (https://metacpan.org/pod/Starlet)
5
+ #
6
+ # ## How to use
7
+ # $ gem install pico_http_parser
8
+ # $ gem isntall prefork_engine
9
+ # $ cat config.ru
10
+ # class HelloApp
11
+ # def call(env)
12
+ # [
13
+ # 200,
14
+ # { 'Content-Type' => 'text/html' },
15
+ # ['hello world ']
16
+ # ]
17
+ # end
18
+ # end
19
+ #
20
+ # run HelloApp.new
21
+ #
22
+ # $ rackup -r ./starlet.rb -E production -s Starlet \
23
+ # -O MaxWorkers=5 -O MaxRequestPerChild=1000 -O MinRequestPerChild=800 config.ru
24
+ #
25
+ # ## LICENSE
26
+ # This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
27
+ # See http://www.perl.com/perl/misc/Artistic.html
28
+
29
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
30
+ require 'rubygems'
31
+ require 'rack'
32
+ require 'stringio'
33
+ require 'socket'
34
+ require 'rack/utils'
35
+ require 'io/nonblock'
36
+ require 'prefork_engine'
37
+ require 'pico_http_parser'
38
+
39
+ module Rack
40
+ module Handler
41
+ class Starlet
42
+ DEFAULT_OPTIONS = {
43
+ :Host => '0.0.0.0',
44
+ :Port => 9292,
45
+ :MaxWorkers => 10,
46
+ :Timeout => 300,
47
+ :MaxRequestPerChild => 100,
48
+ :MinRequestPerChild => nil,
49
+ :SpawnInterval => nil,
50
+ :ErrRespawnInterval => nil
51
+ }
52
+ NULLIO = StringIO.new("").set_encoding('BINARY')
53
+
54
+ def self.run(app, options={})
55
+ slf = new(options)
56
+ slf.setup_listener()
57
+ slf.run_worker(app)
58
+ end
59
+
60
+ def self.valid_options
61
+ {
62
+ "Host=HOST" => "Hostname to listen on (default: 0.0.0.0)",
63
+ "Port=PORT" => "Port to listen on (default: 9292)",
64
+ }
65
+ end
66
+
67
+ def initialize(options={})
68
+ @options = DEFAULT_OPTIONS.merge(options)
69
+ @server = nil
70
+ @_is_tcp = false
71
+ @_using_defer_accept = false
72
+
73
+ end
74
+
75
+ def setup_listener()
76
+ if ENV["SERVER_STARTER_PORT"] then
77
+ hostport, fd = ENV["SERVER_STARTER_PORT"].split("=",2)
78
+ if m = hostport.match(/(.*):(\d+)/) then
79
+ @options[:Host] = m[0]
80
+ @options[:Port] = m[1].to_i
81
+ else
82
+ @options[:Port] = hostport
83
+ end
84
+ @server = TCPServer.for_fd(fd.to_i)
85
+ @_is_tcp = true if !@server.local_address.unix?
86
+ end
87
+
88
+ if @server == nil
89
+ @server = TCPServer.new(@options[:Host], @options[:Port])
90
+ @server.setsockopt(:SOCKET, :REUSEADDR, 1)
91
+ @_is_tcp = true
92
+ end
93
+
94
+ if RUBY_PLATFORM.match(/linux/) && @_is_tcp == true then
95
+ begin
96
+ @server.setsockopt(Socket::IPPROTO_TCP, 9, 1)
97
+ @_using_defer_accept = true
98
+ end
99
+ end
100
+ end
101
+
102
+ def run_worker(app)
103
+ pm_args = {
104
+ "max_workers" => @options[:MaxWorkers].to_i,
105
+ "trap_signals" => {
106
+ "TERM" => 'TERM',
107
+ "HUP" => 'TERM',
108
+ },
109
+ }
110
+ if @options[:SpawnInterval] then
111
+ pm_args["trap_signals"]["USR1"] = ["TERM", @options[:SpawnInterval].to_i]
112
+ pm_args["spawn_interval"] = @options[:SpawnInterval].to_i
113
+ end
114
+ if @options[:ErrRespawnInterval] then
115
+ pm_args["err_respawn_interval"] = @options[:ErrRespawnInterval].to_i
116
+ end
117
+ pe = PreforkEngine.new(pm_args)
118
+ while !pe.signal_received.match(/^(TERM|USR1)$/)
119
+ pe.start {
120
+ srand
121
+ self.accept_loop(app)
122
+ }
123
+ end
124
+ pe.wait_all_children
125
+ end
126
+
127
+ def _calc_reqs_per_child
128
+ max = @options[:MaxRequestPerChild].to_i
129
+ if min = @options[:MinRequestPerChild] then
130
+ return (max - (max - min.to_i + 1) * rand).to_i
131
+ end
132
+ return max.to_i
133
+ end
134
+
135
+ def accept_loop(app)
136
+ @can_exit = true
137
+ @term_received = 0
138
+ proc_req_count = 0
139
+
140
+ Signal.trap('TERM') {
141
+ if @can_exit then
142
+ exit!(true)
143
+ end
144
+ @term_received += 1
145
+ if @can_exit || @term_received > 1 then
146
+ exit!(true)
147
+ end
148
+ }
149
+ Signal.trap('PIPE', 'IGNORE')
150
+ max_reqs = self._calc_reqs_per_child()
151
+ while proc_req_count < max_reqs
152
+ @can_exit = true
153
+ connection = @server.accept
154
+ begin
155
+ connection.nonblock(true) {
156
+ peeraddr = nil
157
+ peerport = 0
158
+ if @_is_tcp then
159
+ connection.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
160
+ peer = connection.peeraddr
161
+ peeraddr = peer[2],
162
+ peerport = peer[1].to_s
163
+ end
164
+ proc_req_count += 1
165
+ @_is_deferred_accept = @_using_defer_accept
166
+ env = {
167
+ 'SERVER_NAME' => @options[:Host],
168
+ 'SERVER_PORT' => @options[:Port].to_s,
169
+ 'REMOTE_ADDR' => peeraddr,
170
+ 'REMOTE_PORT' => peerport,
171
+ 'rack.version' => [0,1],
172
+ 'rack.errors' => STDERR,
173
+ 'rack.multithread' => false,
174
+ 'rack.multiprocess' => false,
175
+ 'rack.run_once' => false,
176
+ 'rack.url_scheme' => 'http'
177
+ }
178
+ self.handle_connection(env, connection, app)
179
+ }
180
+ ensure
181
+ connection.close
182
+ end
183
+ end
184
+ end
185
+
186
+ def handle_connection(env, connection, app)
187
+ buf = ""
188
+ while true
189
+ readed = self.read_timeout(connection)
190
+ if readed == nil then
191
+ next
192
+ end
193
+ @can_exit = false
194
+ buf += readed
195
+ reqlen = PicoHTTPParser.parse_http_request(buf,env)
196
+ if reqlen >= 0 then
197
+ # force donwgrade to 1.0
198
+ env["SERVER_PROTOCOL"] = "HTTP/1.0"
199
+ # handle request
200
+ if (cl = env["CONTENT_LENGTH"].to_i) > 0 then
201
+ buf = buf.unpack('C*').slice(reqlen..-1).pack('C*')
202
+ buffer = StringIO.new("").set_encoding('BINARY')
203
+ while cl > 0
204
+ chunk = ""
205
+ if buf.bytesize > 0 then
206
+ chunk = buf
207
+ buf = ""
208
+ else
209
+ readed = self.read_timeout(connection)
210
+ if readed == nil then
211
+ return
212
+ end
213
+ chunk += readed
214
+ end
215
+ buffer << chunk
216
+ cl -= chunk.bytesize
217
+ end
218
+ buffer.rewind
219
+ env["rack.input"] = buffer
220
+ else
221
+ env["rack.input"] = NULLIO
222
+ end
223
+ status, header, body = app.call(env)
224
+ res_header = "HTTP/1.0 "+status.to_s+" "+Rack::Utils::HTTP_STATUS_CODES[status]+"\r\nConnection: close\r\n"
225
+ header.each do |k,vs|
226
+ if k.downcase == "connection" then
227
+ next
228
+ end
229
+ res_header += k + ": " + vs + "\r\n"
230
+ end
231
+ res_header += "\r\n"
232
+ if body.length == 1 && body[0].bytesize < 40960 then
233
+ ret = self.write_all(connection,res_header+body[0])
234
+ else
235
+ ret = self.write_all(connection,res_header)
236
+ body.each do |part|
237
+ self.write_all(connection,part)
238
+ end
239
+ end
240
+ return true
241
+ elsif reqlen == -2 then
242
+ # request is incomplete, do nothing
243
+ else
244
+ # error
245
+ return nil
246
+ end
247
+ end
248
+ end
249
+
250
+ def read_timeout(connection)
251
+ if @_is_deferred_accept then
252
+ @_is_deferred_accept = false
253
+ else
254
+ if !IO.select([connection],nil,nil,300) then
255
+ return nil
256
+ end
257
+ end
258
+ while true
259
+ begin
260
+ buf = connection.sysread(4096)
261
+ return buf
262
+ rescue IO::WaitReadable
263
+ # retry
264
+ break
265
+ rescue EOFError
266
+ # closed
267
+ return nil
268
+ end
269
+ end
270
+ end
271
+
272
+ def write_timeout(connection, buf)
273
+ while true
274
+ begin
275
+ len = connection.syswrite(buf)
276
+ return len
277
+ rescue IO::WaitReadable
278
+ # retry
279
+ break
280
+ rescue EOFError
281
+ # closed
282
+ return nil
283
+ end
284
+ end
285
+ if !IO.select(nil,[connection],nil,300) then
286
+ return nil
287
+ end
288
+ end
289
+
290
+ def write_all(connection, buf)
291
+ off = 0
292
+ while buf.bytesize - off > 0
293
+ ret = self.write_timeout(connection,buf.unpack('C*').slice(off..-1).pack('C*'))
294
+ if ret == nil then
295
+ return nil
296
+ end
297
+ off += ret
298
+ end
299
+ return buf.bytesize
300
+ end
301
+
302
+ end
303
+ end
304
+ end
305
+
306
+
@@ -1,3 +1,3 @@
1
1
  class PreforkEngine
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -22,6 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
23
  spec.add_development_dependency "rspec"
24
24
 
25
- spec.add_dependency "pico_http_parser", "~> 0.0.3"
26
25
  spec.add_dependency "proc-wait3", "~> 1.7.2"
27
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefork_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahiro Nagano
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-10 00:00:00.000000000 Z
11
+ date: 2014-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: pico_http_parser
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.0.3
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.0.3
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: proc-wait3
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -90,10 +76,13 @@ files:
90
76
  - ".gitignore"
91
77
  - ".rspec"
92
78
  - ".travis.yml"
79
+ - Changes
93
80
  - Gemfile
94
81
  - LICENSE.txt
95
82
  - README.md
96
83
  - Rakefile
84
+ - example/config.ru
85
+ - example/starlet.rb
97
86
  - lib/prefork_engine.rb
98
87
  - lib/prefork_engine/version.rb
99
88
  - prefork_engine.gemspec