rainbows 0.97.0 → 1.0.0pre1

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 (55) hide show
  1. data/.manifest +14 -2
  2. data/ChangeLog +87 -118
  3. data/GIT-VERSION-FILE +1 -1
  4. data/GIT-VERSION-GEN +1 -1
  5. data/GNUmakefile +1 -1
  6. data/README +1 -1
  7. data/bin/rainbows +15 -20
  8. data/lib/rainbows/actor_spawn.rb +20 -22
  9. data/lib/rainbows/app_pool.rb +89 -93
  10. data/lib/rainbows/base.rb +4 -61
  11. data/lib/rainbows/client.rb +9 -0
  12. data/lib/rainbows/configurator.rb +37 -39
  13. data/lib/rainbows/const.rb +18 -18
  14. data/lib/rainbows/dev_fd_response.rb +2 -1
  15. data/lib/rainbows/error.rb +39 -37
  16. data/lib/rainbows/ev_core.rb +103 -109
  17. data/lib/rainbows/event_machine.rb +188 -196
  18. data/lib/rainbows/fiber/base.rb +69 -88
  19. data/lib/rainbows/fiber/io/compat.rb +13 -0
  20. data/lib/rainbows/fiber/io/methods.rb +49 -0
  21. data/lib/rainbows/fiber/io/pipe.rb +7 -0
  22. data/lib/rainbows/fiber/io/socket.rb +7 -0
  23. data/lib/rainbows/fiber/io.rb +125 -84
  24. data/lib/rainbows/fiber/rev/heartbeat.rb +8 -0
  25. data/lib/rainbows/fiber/rev/kato.rb +22 -0
  26. data/lib/rainbows/fiber/rev/methods.rb +55 -0
  27. data/lib/rainbows/fiber/rev/server.rb +32 -0
  28. data/lib/rainbows/fiber/rev/sleeper.rb +15 -0
  29. data/lib/rainbows/fiber/rev.rb +6 -164
  30. data/lib/rainbows/fiber.rb +23 -5
  31. data/lib/rainbows/fiber_pool.rb +31 -37
  32. data/lib/rainbows/fiber_spawn.rb +21 -28
  33. data/lib/rainbows/http_server.rb +80 -80
  34. data/lib/rainbows/max_body.rb +26 -28
  35. data/lib/rainbows/process_client.rb +61 -0
  36. data/lib/rainbows/queue_pool.rb +19 -22
  37. data/lib/rainbows/read_timeout.rb +28 -0
  38. data/lib/rainbows/rev/client.rb +10 -10
  39. data/lib/rainbows/rev/core.rb +2 -3
  40. data/lib/rainbows/rev/thread.rb +1 -1
  41. data/lib/rainbows/rev_fiber_spawn.rb +21 -24
  42. data/lib/rainbows/revactor.rb +18 -15
  43. data/lib/rainbows/thread_pool.rb +2 -4
  44. data/lib/rainbows/thread_spawn.rb +1 -2
  45. data/lib/rainbows/writer_thread_pool.rb +14 -4
  46. data/lib/rainbows/writer_thread_spawn.rb +14 -4
  47. data/lib/rainbows.rb +7 -15
  48. data/local.mk.sample +3 -11
  49. data/rainbows.gemspec +2 -4
  50. data/t/kgio-pipe-response.ru +10 -0
  51. data/t/t0035-kgio-pipe-response.sh +70 -0
  52. data/t/test_isolate.rb +2 -1
  53. metadata +46 -30
  54. data/lib/rainbows/acceptor.rb +0 -26
  55. data/lib/rainbows/byte_slice.rb +0 -17
@@ -2,103 +2,84 @@
2
2
  # :enddoc:
3
3
  require 'rainbows/fiber/io'
4
4
 
5
- module Rainbows
6
- module Fiber
5
+ module Rainbows::Fiber::Base
7
6
 
8
- # blocked readers (key: fileno, value: Rainbows::Fiber::IO object)
9
- RD = []
7
+ include Rainbows::Base
10
8
 
11
- # blocked writers (key: fileno, value: Rainbows::Fiber::IO object)
12
- WR = []
9
+ # :stopdoc:
10
+ RD = Rainbows::Fiber::RD
11
+ WR = Rainbows::Fiber::WR
12
+ ZZ = Rainbows::Fiber::ZZ
13
+ # :startdoc:
13
14
 
14
- # sleeping fibers go here (key: Fiber object, value: wakeup time)
15
- ZZ = {}.compare_by_identity
15
+ # the scheduler method that powers both FiberSpawn and FiberPool
16
+ # concurrency models. It times out idle clients and attempts to
17
+ # schedules ones that were blocked on I/O. At most it'll sleep
18
+ # for one second (returned by the schedule_sleepers method) which
19
+ # will cause it.
20
+ def schedule(&block)
21
+ ret = begin
22
+ G.tick
23
+ RD.compact.each { |c| c.f.resume } # attempt to time out idle clients
24
+ t = schedule_sleepers
25
+ Kernel.select(RD.compact.concat(LISTENERS), WR.compact, nil, t) or return
26
+ rescue Errno::EINTR
27
+ retry
28
+ rescue Errno::EBADF, TypeError
29
+ LISTENERS.compact!
30
+ raise
31
+ end or return
16
32
 
17
- # puts the current Fiber into uninterruptible sleep for at least
18
- # +seconds+. Unlike Kernel#sleep, this it is not possible to sleep
19
- # indefinitely to be woken up (nobody wants that in a web server,
20
- # right?). Calling this directly is deprecated, use
21
- # Rainbows.sleep(seconds) instead.
22
- def self.sleep(seconds)
23
- ZZ[::Fiber.current] = Time.now + seconds
24
- ::Fiber.yield
25
- end
26
-
27
- # base module used by FiberSpawn and FiberPool
28
- module Base
29
- include Rainbows::Base
30
-
31
- # the scheduler method that powers both FiberSpawn and FiberPool
32
- # concurrency models. It times out idle clients and attempts to
33
- # schedules ones that were blocked on I/O. At most it'll sleep
34
- # for one second (returned by the schedule_sleepers method) which
35
- # will cause it.
36
- def schedule(&block)
37
- ret = begin
38
- G.tick
39
- RD.compact.each { |c| c.f.resume } # attempt to time out idle clients
40
- t = schedule_sleepers
41
- Kernel.select(RD.compact.concat(LISTENERS),
42
- WR.compact, nil, t) or return
43
- rescue Errno::EINTR
44
- retry
45
- rescue Errno::EBADF, TypeError
46
- LISTENERS.compact!
47
- raise
48
- end or return
49
-
50
- # active writers first, then _all_ readers for keepalive timeout
51
- ret[1].concat(RD.compact).each { |c| c.f.resume }
33
+ # active writers first, then _all_ readers for keepalive timeout
34
+ ret[1].concat(RD.compact).each { |c| c.f.resume }
52
35
 
53
- # accept is an expensive syscall, filter out listeners we don't want
54
- (ret[0] & LISTENERS).each(&block)
55
- end
36
+ # accept is an expensive syscall, filter out listeners we don't want
37
+ (ret[0] & LISTENERS).each(&block)
38
+ end
56
39
 
57
- # wakes up any sleepers that need to be woken and
58
- # returns an interval to IO.select on
59
- def schedule_sleepers
60
- max = nil
61
- now = Time.now
62
- fibs = []
63
- ZZ.delete_if { |fib, time|
64
- if now >= time
65
- fibs << fib
66
- else
67
- max = time
68
- false
69
- end
70
- }
71
- fibs.each { |fib| fib.resume }
72
- now = Time.now
73
- max.nil? || max > (now + 1) ? 1 : max - now
40
+ # wakes up any sleepers that need to be woken and
41
+ # returns an interval to IO.select on
42
+ def schedule_sleepers
43
+ max = nil
44
+ now = Time.now
45
+ fibs = []
46
+ ZZ.delete_if { |fib, time|
47
+ if now >= time
48
+ fibs << fib
49
+ else
50
+ max = time
51
+ false
74
52
  end
53
+ }
54
+ fibs.each { |fib| fib.resume }
55
+ now = Time.now
56
+ max.nil? || max > (now + 1) ? 1 : max - now
57
+ end
75
58
 
76
- def wait_headers_readable(client)
77
- io = client.to_io
78
- expire = nil
79
- begin
80
- return io.recv_nonblock(1, Socket::MSG_PEEK)
81
- rescue Errno::EAGAIN
82
- return if expire && expire < Time.now
83
- expire ||= Time.now + G.kato
84
- client.wait_readable
85
- retry
86
- end
87
- end
59
+ def wait_headers_readable(client)
60
+ io = client.to_io
61
+ expire = nil
62
+ begin
63
+ return io.recv_nonblock(1, Socket::MSG_PEEK)
64
+ rescue Errno::EAGAIN
65
+ return if expire && expire < Time.now
66
+ expire ||= Time.now + G.kato
67
+ client.wait_readable
68
+ retry
69
+ end
70
+ end
88
71
 
89
- def process_client(client)
90
- G.cur += 1
91
- super(client) # see Rainbows::Base
92
- ensure
93
- G.cur -= 1
94
- ZZ.delete(client.f)
95
- end
72
+ def process(client)
73
+ G.cur += 1
74
+ process_client(client)
75
+ ensure
76
+ G.cur -= 1
77
+ ZZ.delete(client.f)
78
+ end
96
79
 
97
- def self.setup(klass, app)
98
- require 'rainbows/fiber/body'
99
- klass.__send__(:include, Rainbows::Fiber::Body)
100
- self.const_set(:APP, app)
101
- end
102
- end
80
+ def self.setup(klass, app)
81
+ require 'rainbows/fiber/body'
82
+ klass.__send__(:include, Rainbows::Fiber::Body)
83
+ self.const_set(:APP, app)
103
84
  end
104
85
  end
@@ -0,0 +1,13 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
4
+ # Used to make Rainbows::Fiber::IO behave like 0.97.0 and earlier
5
+ module Rainbows::Fiber::IO::Compat
6
+ def initialize(io, fiber = Fiber.current)
7
+ @to_io, @f = io, fiber
8
+ end
9
+
10
+ def close
11
+ @to_io.close
12
+ end
13
+ end
@@ -0,0 +1,49 @@
1
+ # -*- encoding: binary -*-
2
+ #
3
+ # :enddoc:
4
+
5
+ # this is used to augment Kgio::Socket and Kgio::Pipe-enhanced classes
6
+ # for use with Rainbows! Do no use this directly, see
7
+ # Rainbows::Fiber::IO::Pipe and Rainbows::Fiber::IO::Socket instead.
8
+ module Rainbows::Fiber::IO::Methods
9
+ RD = Rainbows::Fiber::RD
10
+ WR = Rainbows::Fiber::WR
11
+ attr_accessor :f
12
+
13
+ # for wrapping output response bodies
14
+ def each(&block)
15
+ if buf = kgio_read(16384)
16
+ yield buf
17
+ yield buf while kgio_read(16384, buf)
18
+ end
19
+ self
20
+ end
21
+
22
+ def close
23
+ fd = fileno
24
+ RD[fd] = WR[fd] = nil
25
+ super
26
+ end
27
+
28
+ def wait_readable
29
+ fd = fileno
30
+ @f = Fiber.current
31
+ RD[fd] = self
32
+ Fiber.yield
33
+ RD[fd] = nil
34
+ end
35
+
36
+ def wait_writable
37
+ fd = fileno
38
+ @f = Fiber.current
39
+ WR[fd] = self
40
+ Fiber.yield
41
+ WR[fd] = nil
42
+ end
43
+
44
+ def self.included(klass)
45
+ if klass.method_defined?(:kgio_write)
46
+ klass.__send__(:alias_method, :write, :kgio_write)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: binary -*-
2
+ # A Fiber-aware Pipe class, gives users the illusion of a synchronous
3
+ # interface that yields away from the current Fiber whenever
4
+ # the underlying descriptor is blocked on reads or write
5
+ class Rainbows::Fiber::IO::Pipe < Kgio::Pipe
6
+ include Rainbows::Fiber::IO::Methods
7
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: binary -*-
2
+ # A Fiber-aware Socket class, gives users the illusion of a synchronous
3
+ # interface that yields away from the current Fiber whenever
4
+ # the underlying descriptor is blocked on reads or write
5
+ class Rainbows::Fiber::IO::Socket < Kgio::Socket
6
+ include Rainbows::Fiber::IO::Methods
7
+ end
@@ -1,93 +1,134 @@
1
1
  # -*- encoding: binary -*-
2
- module Rainbows
3
- module Fiber
4
-
5
- # A partially complete IO wrapper, this exports an IO.select()-able
6
- # #to_io method and gives users the illusion of a synchronous
7
- # interface that yields away from the current Fiber whenever
8
- # the underlying IO object cannot read or write
9
- class IO < Struct.new(:to_io, :f)
10
- include Rainbows::ByteSlice
11
-
12
- # :stopdoc:
13
- LOCALHOST = Unicorn::HttpRequest::LOCALHOST
14
-
15
- # needed to write errors with
16
- def write_nonblock(buf)
17
- to_io.write_nonblock(buf)
18
- end
19
-
20
- # enough for Rainbows.addr
21
- def peeraddr
22
- to_io.respond_to?(:peeraddr) ? to_io.peeraddr : [ LOCALHOST ]
23
- end
24
-
25
- # for wrapping output response bodies
26
- def each(&block)
27
- if buf = readpartial(16384)
28
- yield buf
29
- yield buf while readpartial(16384, buf)
30
- end
31
- rescue EOFError
32
- self
33
- end
34
-
35
- def close
36
- fileno = to_io.fileno
37
- RD[fileno] = WR[fileno] = nil
38
- to_io.close unless to_io.closed?
39
- end
40
-
41
- def closed?
42
- to_io.closed?
43
- end
44
-
45
- def wait_readable
46
- fileno = to_io.fileno
47
- RD[fileno] = self
48
- ::Fiber.yield
49
- RD[fileno] = nil
50
- end
51
-
52
- def wait_writable
53
- fileno = to_io.fileno
54
- WR[fileno] = self
55
- ::Fiber.yield
56
- WR[fileno] = nil
57
- end
58
-
59
- def write(buf)
60
- begin
61
- (w = to_io.write_nonblock(buf)) == buf.bytesize and return
62
- buf = byte_slice(buf, w..-1)
63
- rescue Errno::EAGAIN
2
+
3
+ # A Fiber-aware IO class, gives users the illusion of a synchronous
4
+ # interface that yields away from the current Fiber whenever
5
+ # the underlying descriptor is blocked on reads or write
6
+ #
7
+ # This is a stable, legacy interface and should be preserved for all
8
+ # future versions of Rainbows! However, new apps should use
9
+ # Rainbows::Fiber::IO::Socket or Rainbows::Fiber::IO::Pipe instead.
10
+
11
+ class Rainbows::Fiber::IO
12
+ attr_accessor :to_io
13
+
14
+ # :stopdoc:
15
+ # see Rainbows::Fiber::IO::Compat for initialize implementation
16
+ class << self
17
+ alias :[] :new
18
+ end
19
+ # :startdoc:
20
+
21
+ # needed to write errors with
22
+ def write_nonblock(buf)
23
+ @to_io.write_nonblock(buf)
24
+ end
25
+
26
+ def kgio_addr
27
+ @to_io.kgio_addr
28
+ end
29
+
30
+ # for wrapping output response bodies
31
+ def each(&block)
32
+ buf = readpartial(16384)
33
+ yield buf
34
+ yield buf while readpartial(16384, buf)
35
+ rescue EOFError
36
+ self
37
+ end
38
+
39
+ def closed?
40
+ @to_io.closed?
41
+ end
42
+
43
+ def fileno
44
+ @to_io.fileno
45
+ end
46
+
47
+ def write(buf)
48
+ if @to_io.respond_to?(:kgio_trywrite)
49
+ begin
50
+ case rv = @to_io.kgio_trywrite(buf)
51
+ when nil
52
+ return
53
+ when String
54
+ buf = rv
55
+ when Kgio::WaitWritable
64
56
  wait_writable
65
- retry
66
- end while true
67
- end
68
-
69
- # used for reading headers (respecting keepalive_timeout)
70
- def read_timeout
71
- expire = nil
72
- begin
73
- to_io.read_nonblock(16384)
74
- rescue Errno::EAGAIN
75
- return if expire && expire < Time.now
76
- expire ||= Time.now + G.kato
77
- wait_readable
78
- retry
79
57
  end
80
- end
58
+ end while true
59
+ else
60
+ begin
61
+ (rv = @to_io.write_nonblock(buf)) == buf.bytesize and return
62
+ buf = byte_slice(buf, rv..-1)
63
+ rescue Errno::EAGAIN
64
+ wait_writable
65
+ end while true
66
+ end
67
+ end
68
+
69
+ def byte_slice(buf, range) # :nodoc:
70
+ if buf.encoding != Encoding::BINARY
71
+ buf.dup.force_encoding(Encoding::BINARY)[range]
72
+ else
73
+ buf[range]
74
+ end
75
+ end
76
+
77
+ # used for reading headers (respecting keepalive_timeout)
78
+ def read_timeout
79
+ expire = nil
80
+ begin
81
+ return @to_io.read_nonblock(16384)
82
+ rescue Errno::EAGAIN
83
+ return if expire && expire < Time.now
84
+ expire ||= Time.now + G.kato
85
+ wait_readable
86
+ end while true
87
+ end
81
88
 
82
- def readpartial(length, buf = "")
83
- begin
84
- to_io.read_nonblock(length, buf)
85
- rescue Errno::EAGAIN
89
+ def readpartial(length, buf = "")
90
+ if @to_io.respond_to?(:kgio_tryread)
91
+ begin
92
+ rv = @to_io.kgio_tryread(length, buf)
93
+ case rv
94
+ when nil
95
+ raise EOFError, "end of file reached", []
96
+ when Kgio::WaitReadable
86
97
  wait_readable
87
- retry
98
+ else
99
+ return rv
88
100
  end
89
- end
90
-
101
+ end while true
102
+ else
103
+ begin
104
+ return @to_io.read_nonblock(length, buf)
105
+ rescue Errno::EAGAIN
106
+ wait_readable
107
+ end while true
91
108
  end
92
109
  end
110
+
111
+ def kgio_read(*args)
112
+ @to_io.kgio_read(*args)
113
+ end
114
+
115
+ def kgio_read!(*args)
116
+ @to_io.kgio_read!(*args)
117
+ end
118
+
119
+ def kgio_trywrite(*args)
120
+ @to_io.kgio_trywrite(*args)
121
+ end
122
+
123
+ autoload :Socket, 'rainbows/fiber/io/socket'
124
+ autoload :Pipe, 'rainbows/fiber/io/pipe'
93
125
  end
126
+
127
+ # :stopdoc:
128
+ require 'rainbows/fiber/io/methods'
129
+ require 'rainbows/fiber/io/compat'
130
+ Rainbows::Client.__send__(:include, Rainbows::Fiber::IO::Methods)
131
+ Rainbows::Fiber::IO.__send__(:include, Rainbows::Fiber::IO::Compat)
132
+ Rainbows::Fiber::IO.__send__(:include, Rainbows::Fiber::IO::Methods)
133
+ Kgio.wait_readable = :wait_readable
134
+ Kgio.wait_writable = :wait_writable
@@ -0,0 +1,8 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::Fiber::Rev::Heartbeat < Rev::TimerWatcher
4
+ G = Rainbows::G
5
+ def on_timer
6
+ exit if (! G.tick && G.cur <= 0)
7
+ end
8
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ # keep-alive timeout class
4
+ class Rainbows::Fiber::Rev::Kato < Rev::TimerWatcher
5
+ def initialize
6
+ @watch = []
7
+ super(1, true)
8
+ end
9
+
10
+ def <<(fiber)
11
+ @watch << fiber
12
+ enable unless enabled?
13
+ end
14
+
15
+ def on_timer
16
+ @watch.uniq!
17
+ while f = @watch.shift
18
+ f.resume if f.alive?
19
+ end
20
+ disable
21
+ end
22
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ module Rainbows::Fiber::Rev::Methods
4
+ class Watcher < Rev::IOWatcher
5
+ def initialize(fio, flag)
6
+ @f = fio.f || Fiber.current
7
+ super(fio, flag)
8
+ attach(Rev::Loop.default)
9
+ end
10
+
11
+ def on_readable
12
+ @f.resume
13
+ end
14
+
15
+ alias on_writable on_readable
16
+ end
17
+
18
+ def initialize(*args)
19
+ @f = Fiber.current
20
+ super(*args)
21
+ @r = @w = false
22
+ end
23
+
24
+ def close
25
+ @w.detach if @w
26
+ @r.detach if @r
27
+ @r = @w = false
28
+ super
29
+ end
30
+
31
+ def wait_writable
32
+ @w ||= Watcher.new(self, :w)
33
+ @w.enable unless @w.enabled?
34
+ Fiber.yield
35
+ @w.disable
36
+ end
37
+
38
+ def wait_readable
39
+ @r ||= Watcher.new(self, :r)
40
+ @r.enable unless @r.enabled?
41
+ KATO << @f
42
+ Fiber.yield
43
+ @r.disable
44
+ end
45
+ end
46
+
47
+ [
48
+ Rainbows::Fiber::IO,
49
+ Rainbows::Client,
50
+ # the next two trigger autoload, ugh, oh well...
51
+ Rainbows::Fiber::IO::Socket,
52
+ Rainbows::Fiber::IO::Pipe
53
+ ].each do |klass|
54
+ klass.__send__(:include, Rainbows::Fiber::Rev::Methods)
55
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::Fiber::Rev::Server < Rev::IOWatcher
4
+ G = Rainbows::G
5
+ include Rainbows::ProcessClient
6
+
7
+ def to_io
8
+ @io
9
+ end
10
+
11
+ def initialize(io)
12
+ @io = io
13
+ super(self, :r)
14
+ end
15
+
16
+ def close
17
+ detach if attached?
18
+ @io.close
19
+ end
20
+
21
+ def on_readable
22
+ return if G.cur >= MAX
23
+ c = @io.kgio_tryaccept and Fiber.new { process(c) }.resume
24
+ end
25
+
26
+ def process(io)
27
+ G.cur += 1
28
+ process_client(io)
29
+ ensure
30
+ G.cur -= 1
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::Fiber::Rev::Sleeper < Rev::TimerWatcher
4
+
5
+ def initialize(seconds)
6
+ @f = Fiber.current
7
+ super(seconds, false)
8
+ attach(Rev::Loop.default)
9
+ Fiber.yield
10
+ end
11
+
12
+ def on_timer
13
+ @f.resume
14
+ end
15
+ end