rainbows 4.4.3 → 4.5.0

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.
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