rainbows 4.4.3 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.document +1 -0
  2. data/.gitignore +1 -0
  3. data/Documentation/rainbows.1.txt +8 -4
  4. data/GIT-VERSION-GEN +34 -35
  5. data/GNUmakefile +4 -2
  6. data/HACKING +72 -0
  7. data/bin/rainbows +5 -0
  8. data/lib/rainbows/const.rb +3 -3
  9. data/lib/rainbows/coolio/client.rb +18 -6
  10. data/lib/rainbows/coolio/thread_client.rb +2 -0
  11. data/lib/rainbows/epoll/client.rb +35 -12
  12. data/lib/rainbows/ev_core.rb +5 -4
  13. data/lib/rainbows/event_machine/client.rb +9 -4
  14. data/lib/rainbows/process_client.rb +7 -3
  15. data/lib/rainbows/response.rb +54 -18
  16. data/lib/rainbows/revactor/client/methods.rb +1 -1
  17. data/lib/rainbows/stream_response_epoll.rb +34 -15
  18. data/lib/rainbows/stream_response_epoll/client.rb +11 -3
  19. data/lib/rainbows/writer_thread_pool/client.rb +2 -0
  20. data/lib/rainbows/xepoll/client.rb +0 -3
  21. data/lib/rainbows/xepoll_thread_pool/client.rb +1 -1
  22. data/lib/rainbows/xepoll_thread_spawn/client.rb +1 -1
  23. data/rainbows.gemspec +6 -3
  24. data/t/GNUmakefile +10 -3
  25. data/t/byte-range-common.sh +1 -1
  26. data/t/hijack.ru +56 -0
  27. data/t/t0000-simple-http.sh +9 -9
  28. data/t/t0001-unix-http.sh +8 -8
  29. data/t/t0003-reopen-logs.sh +8 -8
  30. data/t/t0004-heartbeat-timeout.sh +3 -3
  31. data/t/t0005-large-file-response.sh +1 -1
  32. data/t/t0010-keepalive-timeout-effective.sh +2 -2
  33. data/t/t0011-close-on-exec-set.sh +2 -2
  34. data/t/t0017-keepalive-timeout-zero.sh +2 -2
  35. data/t/t0024-pipelined-sendfile-response.sh +2 -2
  36. data/t/t0027-nil-copy_stream.sh +1 -1
  37. data/t/t0030-fast-pipe-response.sh +1 -1
  38. data/t/t0034-pipelined-pipe-response.sh +2 -2
  39. data/t/t0035-kgio-pipe-response.sh +1 -1
  40. data/t/t0040-keepalive_requests-setting.sh +4 -4
  41. data/t/t0043-quit-keepalive-disconnect.sh +3 -3
  42. data/t/t0044-autopush.sh +2 -2
  43. data/t/t0045-client_max_header_size.sh +2 -2
  44. data/t/t0100-rack-input-hammer-chunked.sh +4 -4
  45. data/t/t0100-rack-input-hammer-content-length.sh +4 -4
  46. data/t/t0106-rack-input-keepalive.sh +6 -6
  47. data/t/t0200-async-response.sh +5 -5
  48. data/t/t0202-async-response-one-oh.sh +5 -5
  49. data/t/t0300-async_sinatra.sh +5 -5
  50. data/t/t0400-em-async-app.sh +2 -2
  51. data/t/t0401-em-async-tailer.sh +2 -2
  52. data/t/t0402-async-keepalive.sh +23 -23
  53. data/t/t0500-cramp-streaming.sh +3 -3
  54. data/t/t0600-rack-fiber_pool.sh +1 -1
  55. data/t/t0700-app-deferred.sh +2 -2
  56. data/t/t0800-rack-hijack.sh +27 -0
  57. data/t/t9000-rack-app-pool.sh +3 -3
  58. data/t/t9001-sendfile-to-path.sh +1 -1
  59. data/t/t9100-thread-timeout.sh +1 -1
  60. data/t/t9101-thread-timeout-threshold.sh +1 -1
  61. data/t/test-lib.sh +15 -0
  62. data/t/test_isolate.rb +4 -3
  63. metadata +26 -6
  64. data/t/bin/utee +0 -12
data/.document CHANGED
@@ -46,3 +46,4 @@ Summary
46
46
  Test_Suite
47
47
  Static_Files
48
48
  Sandbox
49
+ HACKING
data/.gitignore CHANGED
@@ -19,3 +19,4 @@ pkg/
19
19
  /LATEST
20
20
  tags
21
21
  TAGS
22
+ /lib/rainbows/version.rb
@@ -58,6 +58,10 @@ with rackup(1) but strongly discouraged.
58
58
  For production deployments, specifying the "listen" directive in
59
59
  CONFIG_FILE is recommended as it allows fine-tuning of socket
60
60
  options.
61
+ -N, \--no-default-middleware
62
+ : Disables loading middleware implied by RACK_ENV. This bypasses the
63
+ configuration documented in the RACK ENVIRONMENT section, but still
64
+ allows RACK_ENV to be used for application/framework-specific purposes.
61
65
 
62
66
  # RACKUP COMPATIBILITY OPTIONS
63
67
  -o, \--host HOST
@@ -137,10 +141,10 @@ All unrecognized values for RACK_ENV are assumed to be
137
141
  "none". Production deployments are strongly encouraged to use
138
142
  "deployment" or "none" for maximum performance.
139
143
 
140
- Note that the Rack::ContentLength and Rack::Chunked middlewares
141
- are never loaded by default. If needed, they should be
142
- individually specified in the RACKUP_FILE, some frameworks do
143
- not require them.
144
+ Note the Rack::ContentLength and Rack::Chunked middlewares are also
145
+ loaded by "deployment" and "development", but no other values of
146
+ RACK_ENV. If needed, they must be individually specified in the
147
+ RACKUP_FILE, some frameworks do not require them.
144
148
 
145
149
  # SEE ALSO
146
150
 
@@ -1,40 +1,39 @@
1
- #!/bin/sh
2
-
3
- GVF=GIT-VERSION-FILE
4
- DEF_VER=v4.4.3
5
-
6
- LF='
7
- '
1
+ #!/usr/bin/env ruby
2
+ DEF_VER = "v4.5.0"
3
+ CONSTANT = "Rainbows::Const::RAINBOWS_VERSION"
4
+ RVF = "lib/rainbows/version.rb"
5
+ GVF = "GIT-VERSION-FILE"
6
+ vn = DEF_VER
8
7
 
9
8
  # First see if there is a version file (included in release tarballs),
10
9
  # then try git-describe, then default.
11
- if test -f version
12
- then
13
- VN=$(cat version) || VN="$DEF_VER"
14
- elif test -d .git -o -f .git &&
15
- VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
16
- case "$VN" in
17
- *$LF*) (exit 1) ;;
18
- v[0-9]*)
19
- git update-index -q --refresh
20
- test -z "$(git diff-index --name-only HEAD --)" ||
21
- VN="$VN-dirty" ;;
22
- esac
23
- then
24
- VN=$(echo "$VN" | sed -e 's/-/./g');
25
- else
26
- VN="$DEF_VER"
27
- fi
10
+ if File.exist?(".git")
11
+ describe = `git describe --abbrev=4 HEAD 2>/dev/null`.strip
12
+ case describe
13
+ when /\Av[0-9]*/
14
+ vn = describe
15
+ system(*%w(git update-index -q --refresh))
16
+ unless `git diff-index --name-only HEAD --`.chomp.empty?
17
+ vn << "-dirty"
18
+ end
19
+ vn.tr!('-', '.')
20
+ end
21
+ end
22
+
23
+ vn = vn.sub!(/\Av/, "")
24
+
25
+ # generate the Ruby constant
26
+ new_ruby_version = "#{CONSTANT} = '#{vn}'\n"
27
+ cur_ruby_version = File.read(RVF) rescue nil
28
+ if new_ruby_version != cur_ruby_version
29
+ File.open(RVF, "w") { |fp| fp.write(new_ruby_version) }
30
+ end
28
31
 
29
- VN=$(expr "$VN" : v*'\(.*\)')
32
+ # generate the makefile snippet
33
+ new_make_version = "GIT_VERSION = #{vn}\n"
34
+ cur_make_version = File.read(GVF) rescue nil
35
+ if new_make_version != cur_make_version
36
+ File.open(GVF, "w") { |fp| fp.write(new_make_version) }
37
+ end
30
38
 
31
- if test -r $GVF
32
- then
33
- VC=$(sed -e 's/^GIT_VERSION = //' <$GVF)
34
- else
35
- VC=unset
36
- fi
37
- test "$VN" = "$VC" || {
38
- echo >&2 "GIT_VERSION = $VN"
39
- echo "GIT_VERSION = $VN" >$GVF
40
- }
39
+ puts vn if $0 == __FILE__
@@ -28,14 +28,16 @@ clean:
28
28
  man html:
29
29
  $(MAKE) -C Documentation install-$@
30
30
 
31
- pkg_extra += $(man1_paths)
31
+ pkg_extra += $(man1_paths) lib/rainbows/version.rb
32
32
 
33
33
  doc::
34
34
  cat Documentation/comparison.css >> doc/rdoc.css
35
35
  $(RM) $(man1_rdoc)
36
36
 
37
+ lib/rainbows/version.rb: GIT-VERSION-FILE
38
+
37
39
  all:: test
38
- test:
40
+ test: lib/rainbows/version.rb
39
41
  $(MAKE) -C t
40
42
 
41
43
  .PHONY: man html
data/HACKING ADDED
@@ -0,0 +1,72 @@
1
+ = Rainbows! Hacker's Guide
2
+
3
+ === Tests
4
+
5
+ All tests are written in POSIX shell. See README file in the t/ directory.
6
+
7
+ === Documentation
8
+
9
+ We use RDoc 3.9.x with Darkfish for documentation as much as possible,
10
+ if you're on Ruby 1.8 you want to install the latest "rdoc" gem. Due to
11
+ the lack of RDoc-to-manpage converters we know about, we're writing
12
+ manpages in Markdown and converting to troff/HTML with Pandoc.
13
+
14
+ Please wrap documentation at 72 characters-per-line or less (long URLs
15
+ are exempt) so it is comfortably readable from terminals.
16
+
17
+ When referencing mailing list posts, use
18
+ "http://mid.gmane.org/$MESSAGE_ID" if possible since the Message-ID
19
+ remains searchable even if Gmane becomes unavailable.
20
+
21
+ == Contributing
22
+
23
+ Contributions are welcome in the form of patches, pull requests, code
24
+ review, testing, documentation, user support or any other feedback is
25
+ welcome. The mailing list is the central coordination point for all
26
+ user and developer feedback and bug reports.
27
+
28
+ === Submitting Patches
29
+
30
+ Follow conventions already established in the code and do not exceed 80
31
+ characters per line.
32
+
33
+ Inline patches (from "git format-patch -M") to the mailing list are
34
+ preferred because they allow code review and comments in the reply to
35
+ the patch.
36
+
37
+ We will adhere to mostly the same conventions for patch submissions as
38
+ git itself. See the Documentation/SubmittingPatches document
39
+ distributed with git on on patch submission guidelines to follow. Just
40
+ don't email the git mailing list or maintainer with Rainbows! patches :)
41
+
42
+ No subscription is required to post to the mailing list at
43
+ rainbows-talk@rubyforge.org
44
+
45
+ Please ask for Cc: if you are not subscribed (Cc:-by-default is uncommon
46
+ on Ruby mailing lists)
47
+
48
+ == Building a Gem
49
+
50
+ In order to build the gem, you must install the following components:
51
+
52
+ * wrongdoc
53
+ * pandoc
54
+
55
+ You can build the Unicorn gem with the following command:
56
+
57
+ gmake gem
58
+
59
+ == Running Development Versions
60
+
61
+ It is easy to install the contents of your git working directory:
62
+
63
+ Via RubyGems (recommended):
64
+
65
+ gmake install-gem
66
+
67
+ Without RubyGems (via setup.rb):
68
+
69
+ ruby setup.rb
70
+
71
+ It is not at all recommended to mix a RubyGems installation with an
72
+ installation done without RubyGems, however.
@@ -59,6 +59,11 @@ op = OptionParser.new("", 24, ' ') do |opts|
59
59
  ENV["RACK_ENV"] = e
60
60
  end
61
61
 
62
+ opts.on("-N", "--no-default-middleware",
63
+ "do not load middleware implied by RACK_ENV") do |e|
64
+ rackup_opts[:no_default_middleware] = true
65
+ end
66
+
62
67
  opts.on("-D", "--daemonize", "run daemonized in the background") do |d|
63
68
  rackup_opts[:daemonize] = !!d
64
69
  end
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: binary -*-
2
2
  # :enddoc:
3
3
  module Rainbows::Const
4
-
5
- RAINBOWS_VERSION = '4.4.3'
6
-
4
+ end
5
+ require 'rainbows/version'
6
+ module Rainbows::Const
7
7
  include Unicorn::Const
8
8
 
9
9
  RACK_DEFAULTS = Unicorn::HttpRequest::DEFAULTS.update({
@@ -86,6 +86,12 @@ class Rainbows::Coolio::Client < Coolio::IO
86
86
  @deferred = true
87
87
  end
88
88
 
89
+ def hijacked
90
+ CONN.delete(self)
91
+ detach
92
+ nil
93
+ end
94
+
89
95
  def write_response_path(status, headers, body, alive)
90
96
  io = body_to_io(body)
91
97
  st = io.stat
@@ -93,7 +99,8 @@ class Rainbows::Coolio::Client < Coolio::IO
93
99
  if st.file?
94
100
  defer_file(status, headers, body, alive, io, st)
95
101
  elsif st.socket? || st.pipe?
96
- chunk = stream_response_headers(status, headers, alive)
102
+ chunk = stream_response_headers(status, headers, alive, body)
103
+ return hijacked if nil == chunk
97
104
  stream_response_body(body, io, chunk)
98
105
  else
99
106
  # char or block device... WTF?
@@ -103,10 +110,11 @@ class Rainbows::Coolio::Client < Coolio::IO
103
110
 
104
111
  def ev_write_response(status, headers, body, alive)
105
112
  if body.respond_to?(:to_path)
106
- write_response_path(status, headers, body, alive)
113
+ body = write_response_path(status, headers, body, alive)
107
114
  else
108
- write_response(status, headers, body, alive)
115
+ body = write_response(status, headers, body, alive)
109
116
  end
117
+ return hijacked unless body
110
118
  return quit unless alive && :close != @state
111
119
  @state = :headers
112
120
  end
@@ -117,9 +125,11 @@ class Rainbows::Coolio::Client < Coolio::IO
117
125
  @env[RACK_INPUT] = input
118
126
  @env[REMOTE_ADDR] = @_io.kgio_addr
119
127
  @env[ASYNC_CALLBACK] = method(:write_async_response)
128
+ @hp.hijack_setup(@env, @_io)
120
129
  status, headers, body = catch(:async) {
121
130
  APP.call(@env.merge!(RACK_DEFAULTS))
122
131
  }
132
+ return hijacked if @hp.hijacked?
123
133
 
124
134
  (nil == status || -1 == status) ? @deferred = true :
125
135
  ev_write_response(status, headers, body, @hp.next?)
@@ -186,12 +196,13 @@ class Rainbows::Coolio::Client < Coolio::IO
186
196
  def defer_file(status, headers, body, alive, io, st)
187
197
  if r = sendfile_range(status, headers)
188
198
  status, headers, range = r
189
- write_headers(status, headers, alive)
199
+ body = write_headers(status, headers, alive, body) or return hijacked
190
200
  range and defer_file_stream(range[0], range[1], io, body)
191
201
  else
192
- write_headers(status, headers, alive)
202
+ write_headers(status, headers, alive, body) or return hijacked
193
203
  defer_file_stream(0, st.size, io, body)
194
204
  end
205
+ body
195
206
  end
196
207
 
197
208
  def stream_file_chunk(sf) # +sf+ is a Rainbows::StreamFile object
@@ -207,8 +218,9 @@ class Rainbows::Coolio::Client < Coolio::IO
207
218
  end
208
219
  else
209
220
  def defer_file(status, headers, body, alive, io, st)
210
- write_headers(status, headers, alive)
221
+ write_headers(status, headers, alive, body) or return hijacked
211
222
  defer_file_stream(0, st.size, io, body)
223
+ body
212
224
  end
213
225
 
214
226
  def stream_file_chunk(body)
@@ -14,6 +14,7 @@ class Rainbows::Coolio::ThreadClient < Rainbows::Coolio::Client
14
14
 
15
15
  # this is only called in the master thread
16
16
  def response_write(response)
17
+ return hijacked if @hp.hijacked?
17
18
  ev_write_response(*response, @hp.next?)
18
19
  rescue => e
19
20
  handle_error(e)
@@ -25,6 +26,7 @@ class Rainbows::Coolio::ThreadClient < Rainbows::Coolio::Client
25
26
  def app_response
26
27
  begin
27
28
  @env[REMOTE_ADDR] = @_io.kgio_addr
29
+ @hp.hijack_setup(@env, @_io)
28
30
  APP.call(@env.merge!(RACK_DEFAULTS))
29
31
  rescue => e
30
32
  Rainbows::Error.app(e) # we guarantee this does not raise
@@ -6,14 +6,14 @@ module Rainbows::Epoll::Client
6
6
  include Rainbows::EvCore
7
7
  APP = Rainbows.server.app
8
8
  Server = Rainbows::Epoll::Server
9
- IN = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ET
10
- OUT = SleepyPenguin::Epoll::OUT | SleepyPenguin::Epoll::ET
9
+ IN = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ONESHOT
10
+ OUT = SleepyPenguin::Epoll::OUT | SleepyPenguin::Epoll::ONESHOT
11
+ EPINOUT = IN | OUT
11
12
  KATO = {}
12
13
  KATO.compare_by_identity if KATO.respond_to?(:compare_by_identity)
13
14
  Rainbows.at_quit { KATO.each_key { |k| k.timeout! }.clear }
14
15
  Rainbows.config!(self, :keepalive_timeout)
15
16
  EP = Rainbows::EP
16
- ReRun = []
17
17
  @@last_expire = Time.now
18
18
 
19
19
  def self.expire
@@ -28,9 +28,6 @@ module Rainbows::Epoll::Client
28
28
  def self.loop
29
29
  begin
30
30
  EP.wait(nil, 1000) { |_, obj| obj.epoll_run }
31
- while obj = ReRun.shift
32
- obj.epoll_run
33
- end
34
31
  expire
35
32
  rescue Errno::EINTR
36
33
  rescue => e
@@ -52,6 +49,7 @@ module Rainbows::Epoll::Client
52
49
  when String
53
50
  on_read(rv)
54
51
  return if @wr_queue[0] || closed?
52
+ return hijacked if @hp.hijacked?
55
53
  when :wait_readable
56
54
  KATO[self] = @@last_expire if :headers == @state
57
55
  return EP.set(self, IN)
@@ -67,7 +65,9 @@ module Rainbows::Epoll::Client
67
65
  def app_call input # called by on_read()
68
66
  @env[RACK_INPUT] = input
69
67
  @env[REMOTE_ADDR] = kgio_addr
68
+ @hp.hijack_setup(@env, self)
70
69
  status, headers, body = APP.call(@env.merge!(RACK_DEFAULTS))
70
+ return hijacked if @hp.hijacked?
71
71
  ev_write_response(status, headers, body, @hp.next?)
72
72
  end
73
73
 
@@ -78,7 +78,8 @@ module Rainbows::Epoll::Client
78
78
  if st.file?
79
79
  defer_file(status, headers, body, alive, io, st)
80
80
  elsif st.socket? || st.pipe?
81
- chunk = stream_response_headers(status, headers, alive)
81
+ chunk = stream_response_headers(status, headers, alive, body)
82
+ return hijacked if nil == chunk
82
83
  stream_response_body(body, io, chunk)
83
84
  else
84
85
  # char or block device... WTF?
@@ -102,7 +103,28 @@ module Rainbows::Epoll::Client
102
103
  else
103
104
  write_response(status, headers, body, alive)
104
105
  end
105
- on_read(Z) if alive && 0 == @wr_queue.size && 0 != @buf.size
106
+ return hijacked if @hp.hijacked?
107
+ # try to read more if we didn't have to buffer writes
108
+ next_request if alive && 0 == @wr_queue.size
109
+ end
110
+
111
+ def hijacked
112
+ KATO.delete(self)
113
+ Server.decr # no other place to do this
114
+ EP.delete(self)
115
+ nil
116
+ end
117
+
118
+ def next_request
119
+ if 0 == @buf.size
120
+ want_more
121
+ else
122
+ # pipelined request (already in buffer)
123
+ on_read(Z)
124
+ return if @wr_queue[0] || closed?
125
+ return hijacked if @hp.hijacked?
126
+ close if :close == @state
127
+ end
106
128
  end
107
129
 
108
130
  def epoll_run
@@ -115,12 +137,12 @@ module Rainbows::Epoll::Client
115
137
  end
116
138
 
117
139
  def want_more
118
- ReRun << self
140
+ EP.set(self, EPINOUT)
119
141
  end
120
142
 
121
143
  def on_deferred_write_complete
122
144
  :close == @state and return close
123
- 0 == @buf.size ? on_readable : on_read(Z)
145
+ next_request
124
146
  end
125
147
 
126
148
  def handle_error(e)
@@ -185,13 +207,14 @@ module Rainbows::Epoll::Client
185
207
  true
186
208
  end
187
209
 
210
+ # Rack apps should not hijack here, but they may...
188
211
  def defer_file(status, headers, body, alive, io, st)
189
212
  if r = sendfile_range(status, headers)
190
213
  status, headers, range = r
191
- write_headers(status, headers, alive)
214
+ write_headers(status, headers, alive, body) or return hijacked
192
215
  range and defer_file_stream(range[0], range[1], io, body)
193
216
  else
194
- write_headers(status, headers, alive)
217
+ write_headers(status, headers, alive, body) or return hijacked
195
218
  defer_file_stream(0, st.size, io, body)
196
219
  end
197
220
  end
@@ -52,16 +52,17 @@ module Rainbows::EvCore
52
52
  end
53
53
 
54
54
  # returns whether to enable response chunking for autochunk models
55
- def stream_response_headers(status, headers, alive)
55
+ # returns nil if request was hijacked in response stage
56
+ def stream_response_headers(status, headers, alive, body)
56
57
  headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
57
58
  if headers.include?(Content_Length)
58
- write_headers(status, headers, alive)
59
+ write_headers(status, headers, alive, body) or return
59
60
  return false
60
61
  end
61
62
 
62
63
  case @env[HTTP_VERSION]
63
64
  when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
64
- write_headers(status, headers, false)
65
+ write_headers(status, headers, false, body) or return
65
66
  @hp.clear
66
67
  false
67
68
  when nil # "HTTP/0.9"
@@ -69,7 +70,7 @@ module Rainbows::EvCore
69
70
  else
70
71
  rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
71
72
  rv = false unless @env["rainbows.autochunk"]
72
- write_headers(status, headers, alive)
73
+ write_headers(status, headers, alive, body) or return
73
74
  rv
74
75
  end
75
76
  end