right_speed 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -2
- data/bin/right_speed +1 -3
- data/lib/right_speed/connection_closer.rb +8 -0
- data/lib/right_speed/listener.rb +13 -3
- data/lib/right_speed/processor.rb +43 -13
- data/lib/right_speed/server.rb +3 -10
- data/lib/right_speed/version.rb +1 -1
- data/lib/right_speed/worker/accepter.rb +13 -13
- data/lib/right_speed/worker/base.rb +2 -4
- data/lib/right_speed/worker/fair.rb +35 -0
- data/lib/right_speed/worker/{reader.rb → roundrobin.rb} +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a0fe25cf32e28c2aac8ee6c2621bd3d4dafef2823fffa4063e7ef6a3215f8f8
|
4
|
+
data.tar.gz: 9f77da3078ba68ac534e4124f858920d24d2b78b8b5077e4ebd5d7d874613acf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ff327d684d9ac4e0404640512e5c40250c5a33bc89018f624257da6239d787527382e232b0b09845bbf5e96d466e12bd9968f2a07724c9f5ebcbc10556e5500
|
7
|
+
data.tar.gz: efa47cb44fd9fbf9b370cc451fde661aca0a7b2ea9bbb896780fcf860c6957dce9e20c87b39f1c027df823202fad926194dcdf680fdc948639dfbdf3edd843ca
|
data/README.md
CHANGED
@@ -12,8 +12,17 @@ Currently, RightSpeed supports the very limited set of Rack protocol specificati
|
|
12
12
|
* Handling multipart contents flexisbly (using `rack.multipart.buffer_size` nor `rack.multipart.tempfile_factory`)
|
13
13
|
* [Hijacking](https://github.com/rack/rack/blob/master/SPEC.rdoc#label-Hijacking)
|
14
14
|
|
15
|
+
### Is Ractor-based server faster than prefork processes?
|
16
|
+
|
17
|
+
It can be. In our opinion, it may not be a tremendous difference, but could be a little improvement because:
|
18
|
+
* Accepted connection delivery inter-Ractor should be faster than bringing those over IPC
|
19
|
+
* JIT compilation can be just once using multiple Ractor
|
20
|
+
* ... and?
|
21
|
+
|
15
22
|
## Changelog
|
16
23
|
|
24
|
+
* v0.2.0:
|
25
|
+
* Add worker-type "fair" and "accept" in addition to "roundrobin"
|
17
26
|
* v0.1.0:
|
18
27
|
* The first release just before RubyKaigi Takeout 2021
|
19
28
|
|
@@ -27,7 +36,7 @@ Install `right_speed` by `gem` command (`gem i right_speed`), then use it direct
|
|
27
36
|
$ right_speed -c config.ru -p 8080 --workers 8
|
28
37
|
|
29
38
|
# See right_speed --help for full options:
|
30
|
-
$ right_speed --help
|
39
|
+
$ bundle exec ruby bin/right_speed --help
|
31
40
|
Usage: right_speed [options]
|
32
41
|
|
33
42
|
OPTIONS
|
@@ -35,7 +44,7 @@ OPTIONS
|
|
35
44
|
--port, -p PORT The port number to listen (default: 8080)
|
36
45
|
--backlog NUM The number of backlog
|
37
46
|
--workers NUM The number of Ractors (default: CPU cores)
|
38
|
-
--worker-type TYPE The type of workers
|
47
|
+
--worker-type TYPE The type of workers (available: roundrobin/fair/accept, default: roundrobin)
|
39
48
|
--help Show this message
|
40
49
|
```
|
41
50
|
|
@@ -47,6 +56,22 @@ $ rackup config.ru -s right_speed -p 8080 -O Workers=8
|
|
47
56
|
|
48
57
|
The default number of worker Ractors is the number of CPU cores.
|
49
58
|
|
59
|
+
### Worker Types
|
60
|
+
|
61
|
+
The `--worker-type` option is to try some patterns of use of Ractors.
|
62
|
+
|
63
|
+
* `roundrobin`
|
64
|
+
* Listener Ractor will accept connections, then send those to Worker Ractors in round-robin
|
65
|
+
* Worker Ractors will consume their input connections one-by-one
|
66
|
+
* `fair`
|
67
|
+
* Listener Ractor will accept connections, and yield those to consumers (workers)
|
68
|
+
* Worker Ractors will take connections from Listener as soon as they become available
|
69
|
+
* `accept`
|
70
|
+
* Listener does nothing
|
71
|
+
* Worker Ractors will accept connections, process requests individually
|
72
|
+
|
73
|
+
Currently, any of above workers cannot work well. We observed SEGV or Ruby runtime busy after traffic in seconds.
|
74
|
+
|
50
75
|
## Contributing
|
51
76
|
|
52
77
|
Bug reports and pull requests are welcome on GitHub at https://github.com/tagomoris/right_speed.
|
data/bin/right_speed
CHANGED
@@ -26,10 +26,8 @@ module RightSpeed
|
|
26
26
|
DEFAULT_PORT = Server::DEFAULT_PORT
|
27
27
|
DEFAULT_WORKERS = Env.processors
|
28
28
|
DEFAULT_WORKER_TYPE = Server::DEFAULT_WORKER_TYPE
|
29
|
-
DEFAULT_LISTENER_TYPE = Server::DEFAULT_LISTENER_TYPE
|
30
29
|
|
31
|
-
AVAILABLE_WORKER_TYPES = Server::AVAILABLE_WORKER_TYPES.map(
|
32
|
-
AVAILABLE_LISTENER_TYPES = Server::AVAILABLE_LISTENER_TYPES.map(:to_s).join('/')
|
30
|
+
AVAILABLE_WORKER_TYPES = Server::AVAILABLE_WORKER_TYPES.map(&:to_s).join('/')
|
33
31
|
|
34
32
|
def self.show_help(error: false, error_message: nil)
|
35
33
|
STDERR.puts(error_message, "\n") if error_message
|
@@ -4,6 +4,10 @@ require_relative "logger"
|
|
4
4
|
|
5
5
|
module RightSpeed
|
6
6
|
class ConnectionCloser
|
7
|
+
# This class was introduced to serialize closing connections
|
8
|
+
# (instead of closing those in each Ractor) to try to avoid SEGV.
|
9
|
+
# But SEGV is still happening, so this class may not be valueable.
|
10
|
+
|
7
11
|
def run(workers)
|
8
12
|
@ractor = Ractor.new(workers) do |workers|
|
9
13
|
logger = RightSpeed.logger
|
@@ -23,5 +27,9 @@ module RightSpeed
|
|
23
27
|
logger.error { "Unexpected error, #{e.class}:#{e.message}" }
|
24
28
|
end
|
25
29
|
end
|
30
|
+
|
31
|
+
def wait
|
32
|
+
@ractor.take
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
data/lib/right_speed/listener.rb
CHANGED
@@ -4,10 +4,12 @@ require_relative "logger"
|
|
4
4
|
|
5
5
|
module RightSpeed
|
6
6
|
module Listener
|
7
|
-
def self.setup(
|
8
|
-
case
|
7
|
+
def self.setup(worker_type:, host:, port:, backlog: nil)
|
8
|
+
case worker_type
|
9
9
|
when :roundrobin
|
10
10
|
RoundRobinListener.new(host, port, backlog)
|
11
|
+
when :fair
|
12
|
+
FairListener.new(host, port, backlog)
|
11
13
|
else
|
12
14
|
SimpleListener.new(host, port, backlog)
|
13
15
|
end
|
@@ -23,7 +25,7 @@ module RightSpeed
|
|
23
25
|
@sock = nil
|
24
26
|
end
|
25
27
|
|
26
|
-
def run
|
28
|
+
def run
|
27
29
|
@running = true
|
28
30
|
@sock = TCPServer.open(@host, @port)
|
29
31
|
@sock.listen(@backlog) if @backlog
|
@@ -43,6 +45,8 @@ module RightSpeed
|
|
43
45
|
end
|
44
46
|
|
45
47
|
class RoundRobinListener < SimpleListener
|
48
|
+
attr_reader :ractor
|
49
|
+
|
46
50
|
def run(processor)
|
47
51
|
@running = true
|
48
52
|
@ractor = Ractor.new(@host, @port, @backlog, processor) do |host, port, backlog, processor|
|
@@ -65,5 +69,11 @@ module RightSpeed
|
|
65
69
|
@ractor = nil # TODO: terminate the Ractor if possible
|
66
70
|
end
|
67
71
|
end
|
72
|
+
|
73
|
+
class FairListener < RoundRobinListener
|
74
|
+
def wait
|
75
|
+
# nothing to wait - @ractor.take consumes accepted connections unexpectedly
|
76
|
+
end
|
77
|
+
end
|
68
78
|
end
|
69
79
|
end
|
@@ -3,7 +3,8 @@
|
|
3
3
|
require 'rack/builder'
|
4
4
|
|
5
5
|
require_relative 'worker/accepter'
|
6
|
-
require_relative 'worker/
|
6
|
+
require_relative 'worker/fair'
|
7
|
+
require_relative 'worker/roundrobin'
|
7
8
|
require_relative 'connection_closer'
|
8
9
|
|
9
10
|
module RightSpeed
|
@@ -18,8 +19,10 @@ module RightSpeed
|
|
18
19
|
end
|
19
20
|
handler = Ractor.make_shareable(Handler.new(app))
|
20
21
|
case worker_type
|
21
|
-
when :
|
22
|
-
|
22
|
+
when :roundrobin
|
23
|
+
RoundRobinProcessor.new(workers, handler)
|
24
|
+
when :fair
|
25
|
+
FairProcessor.new(workers, handler)
|
23
26
|
when :accept
|
24
27
|
AcceptProcessor.new(workers, handler)
|
25
28
|
else
|
@@ -63,11 +66,11 @@ module RightSpeed
|
|
63
66
|
end
|
64
67
|
end
|
65
68
|
|
66
|
-
class
|
69
|
+
class RoundRobinProcessor < Base
|
67
70
|
def initialize(workers, handler)
|
68
71
|
@worker_num = workers
|
69
72
|
@handler = handler
|
70
|
-
@workers = workers.times.map{|i| Worker::
|
73
|
+
@workers = workers.times.map{|i| Worker::RoundRobin.new(id: i, handler: @handler)}
|
71
74
|
@closer = ConnectionCloser.new
|
72
75
|
@counter = 0
|
73
76
|
end
|
@@ -89,6 +92,35 @@ module RightSpeed
|
|
89
92
|
|
90
93
|
def wait
|
91
94
|
@workers.each{|w| w.wait}
|
95
|
+
@closer.wait
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class FairProcessor < Base
|
100
|
+
def initialize(workers, handler)
|
101
|
+
@worker_num = workers
|
102
|
+
@handler = handler
|
103
|
+
@workers = workers.times.map{|i| Worker::Fair.new(id: i, handler: @handler)}
|
104
|
+
@closer = ConnectionCloser.new
|
105
|
+
end
|
106
|
+
|
107
|
+
def configure(listener:)
|
108
|
+
@listener = listener
|
109
|
+
end
|
110
|
+
|
111
|
+
def run
|
112
|
+
@listener.run(self)
|
113
|
+
@workers.each{|w| w.run(@listener.ractor)}
|
114
|
+
@closer.run(@workers.map{|w| w.ractor})
|
115
|
+
end
|
116
|
+
|
117
|
+
def process(conn)
|
118
|
+
Ractor.yield(conn, move: true)
|
119
|
+
end
|
120
|
+
|
121
|
+
def wait
|
122
|
+
# listener, workers are using those outgoing to pass connections
|
123
|
+
@closer.wait
|
92
124
|
end
|
93
125
|
end
|
94
126
|
|
@@ -97,24 +129,22 @@ module RightSpeed
|
|
97
129
|
@worker_num = workers
|
98
130
|
@handler = handler
|
99
131
|
@workers = workers.times.map{|i| Worker::Accepter.new(id: i, handler: @handler) }
|
132
|
+
@closer = ConnectionCloser.new
|
100
133
|
end
|
101
134
|
|
102
135
|
def configure(listener:)
|
103
136
|
@listener = listener
|
104
|
-
@workers.each do |w|
|
105
|
-
w.configure(listener.sock)
|
106
|
-
end
|
107
137
|
end
|
108
138
|
|
109
139
|
def run
|
110
|
-
@
|
111
|
-
|
112
|
-
|
113
|
-
# TODO: connection closer
|
140
|
+
@listener.run
|
141
|
+
@workers.each{|w| w.run(@listener.sock)}
|
142
|
+
@closer.run(@workers.map{|w| w.ractor})
|
114
143
|
end
|
115
144
|
|
116
145
|
def wait
|
117
|
-
|
146
|
+
# workers are using those outgoing to pass connections
|
147
|
+
@closer.wait
|
118
148
|
end
|
119
149
|
end
|
120
150
|
end
|
data/lib/right_speed/server.rb
CHANGED
@@ -15,12 +15,10 @@ module RightSpeed
|
|
15
15
|
class Server
|
16
16
|
DEFAULT_HOST = "127.0.0.1"
|
17
17
|
DEFAULT_PORT = 8080
|
18
|
-
DEFAULT_WORKER_TYPE = :
|
18
|
+
DEFAULT_WORKER_TYPE = :roundrobin
|
19
19
|
DEFAULT_WORKERS = Env.processors
|
20
|
-
DEFAULT_SCHEDULER_TYPE = :roundrobin
|
21
20
|
|
22
|
-
AVAILABLE_WORKER_TYPES = [:
|
23
|
-
AVAILABLE_LISTENER_TYPES = [:roundrobin, :fair]
|
21
|
+
AVAILABLE_WORKER_TYPES = [:roundrobin, :fair, :accept]
|
24
22
|
|
25
23
|
attr_reader :config_hooks
|
26
24
|
|
@@ -30,7 +28,6 @@ module RightSpeed
|
|
30
28
|
port: DEFAULT_PORT,
|
31
29
|
workers: DEFAULT_WORKERS,
|
32
30
|
worker_type: DEFAULT_WORKER_TYPE,
|
33
|
-
scheduler_type: DEFAULT_SCHEDULER_TYPE,
|
34
31
|
backlog: nil
|
35
32
|
)
|
36
33
|
@host = host
|
@@ -38,10 +35,6 @@ module RightSpeed
|
|
38
35
|
@app = app
|
39
36
|
@workers = workers
|
40
37
|
@worker_type = worker_type
|
41
|
-
@listener_type = case @worker_type
|
42
|
-
when :read then scheduler_type
|
43
|
-
else :listen
|
44
|
-
end
|
45
38
|
@backlog = backlog
|
46
39
|
@config_hooks = []
|
47
40
|
@logger = nil
|
@@ -63,7 +56,7 @@ module RightSpeed
|
|
63
56
|
|
64
57
|
begin
|
65
58
|
processor = Processor.setup(app: @app, worker_type: @worker_type, workers: @workers)
|
66
|
-
listener = Listener.setup(
|
59
|
+
listener = Listener.setup(worker_type: @worker_type, host: @host, port: @port, backlog: nil)
|
67
60
|
processor.configure(listener: listener)
|
68
61
|
processor.run
|
69
62
|
listener.wait
|
data/lib/right_speed/version.rb
CHANGED
@@ -3,27 +3,27 @@ require_relative 'base'
|
|
3
3
|
module RightSpeed
|
4
4
|
module Worker
|
5
5
|
class Accepter < Base
|
6
|
-
def
|
7
|
-
@
|
8
|
-
|
9
|
-
|
10
|
-
def run
|
11
|
-
@ractor = Ractor.new(@id, @sock) do |id, sock|
|
6
|
+
def run(sock)
|
7
|
+
@ractor = Ractor.new(@id, sock, @handler) do |id, sock, handler|
|
8
|
+
logger = RightSpeed.logger
|
12
9
|
while conn = sock.accept
|
13
10
|
begin
|
14
|
-
|
15
|
-
# TODO:
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
11
|
+
handler.session(conn).process
|
12
|
+
# TODO: keep-alive?
|
13
|
+
Ractor.yield(conn, move: true) # to yield closing connections to ConnectionCloser
|
14
|
+
rescue => e
|
15
|
+
logger.error { "Unexpected error: #{e.message}\n" + e.backtrace.map{"\t#{_1}\n"}.join }
|
16
|
+
# TODO: print backtrace in better way
|
20
17
|
end
|
21
18
|
end
|
19
|
+
logger.info { "Worker#{id}: Finishing the Ractor" }
|
20
|
+
Ractor.yield(:closing) # to tell the outgoing path will be closed when stopping
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
24
|
def wait
|
26
|
-
@ractor.take
|
25
|
+
# nothing to wait - @ractor.take consumes closed connections unexpectedly
|
26
|
+
# @ractor.wait ?
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -4,16 +4,14 @@ require_relative "../handler"
|
|
4
4
|
module RightSpeed
|
5
5
|
module Worker
|
6
6
|
class Base
|
7
|
+
attr_reader :ractor
|
8
|
+
|
7
9
|
def initialize(id:, handler:)
|
8
10
|
@id = id
|
9
11
|
@handler = handler
|
10
12
|
@ractor = nil
|
11
13
|
end
|
12
14
|
|
13
|
-
def ractor
|
14
|
-
@ractor
|
15
|
-
end
|
16
|
-
|
17
15
|
def stop
|
18
16
|
@ractor # TODO: terminate if possible
|
19
17
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require_relative "../logger"
|
3
|
+
|
4
|
+
module RightSpeed
|
5
|
+
module Worker
|
6
|
+
class Fair < Base
|
7
|
+
def run(listener_ractor)
|
8
|
+
@ractor = Ractor.new(@id, @handler, listener_ractor) do |id, handler, listener|
|
9
|
+
logger = RightSpeed.logger
|
10
|
+
while conn = listener.take
|
11
|
+
begin
|
12
|
+
handler.session(conn).process
|
13
|
+
# TODO: keep-alive?
|
14
|
+
Ractor.yield(conn, move: true) # to yield closing connections to ConnectionCloser
|
15
|
+
rescue => e
|
16
|
+
logger.error { "Unexpected error: #{e.message}\n" + e.backtrace.map{"\t#{_1}\n"}.join }
|
17
|
+
# TODO: print backtrace in better way
|
18
|
+
end
|
19
|
+
end
|
20
|
+
logger.info { "Worker#{id}: Finishing the Ractor" }
|
21
|
+
Ractor.yield(:closing) # to tell the outgoing path will be closed when stopping
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process(conn)
|
26
|
+
raise "BUG: Worker::Fair#process should never be called"
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait
|
30
|
+
# nothing to wait - @ractor.take consumes closed connections unexpectedly
|
31
|
+
# @ractor.wait ?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: right_speed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Satoshi Moris Tagomori
|
@@ -109,7 +109,8 @@ files:
|
|
109
109
|
- lib/right_speed/version.rb
|
110
110
|
- lib/right_speed/worker/accepter.rb
|
111
111
|
- lib/right_speed/worker/base.rb
|
112
|
-
- lib/right_speed/worker/
|
112
|
+
- lib/right_speed/worker/fair.rb
|
113
|
+
- lib/right_speed/worker/roundrobin.rb
|
113
114
|
- right_speed.gemspec
|
114
115
|
homepage: https://github.com/tagomoris/right_speed
|
115
116
|
licenses:
|