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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a21c10b045dfb5591600c16d61a7fa9d09771db4960e0d3c3021b90c8fbdc48c
4
- data.tar.gz: a71b06cde0b6b1028aabdab601673d827040824db08d6fce181c9c9816669d74
3
+ metadata.gz: 4a0fe25cf32e28c2aac8ee6c2621bd3d4dafef2823fffa4063e7ef6a3215f8f8
4
+ data.tar.gz: 9f77da3078ba68ac534e4124f858920d24d2b78b8b5077e4ebd5d7d874613acf
5
5
  SHA512:
6
- metadata.gz: 490fb8ff447db76d7cdc16980d547de1b078d9648afe9534804a638abb708d63f572b84a64489707fedad4dfd29caf046b96456b390064ada334a7b14953b556
7
- data.tar.gz: 52e3d420c923a0593f463aeff793911000bce92b8855723f02fa7ffc4e3da3744779d264e29f5ee0b4e46f5a63e5570a39c33f4c79d8c1cd3c2f4e718c6315bf
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, available options are read/accept (default: read)
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(:to_s).join('/')
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
@@ -4,10 +4,12 @@ require_relative "logger"
4
4
 
5
5
  module RightSpeed
6
6
  module Listener
7
- def self.setup(listener_type:, host:, port:, backlog: nil)
8
- case listener_type
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(_processor)
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/reader'
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 :read
22
- ReadProcessor.new(workers, handler)
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 ReadProcessor < Base
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::Reader.new(id: i, handler: @handler)}
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
- @workers.each do |w|
111
- w.run
112
- end
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
- @workers.each{|w| w.wait}
146
+ # workers are using those outgoing to pass connections
147
+ @closer.wait
118
148
  end
119
149
  end
120
150
  end
@@ -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 = :read
18
+ DEFAULT_WORKER_TYPE = :roundrobin
19
19
  DEFAULT_WORKERS = Env.processors
20
- DEFAULT_SCHEDULER_TYPE = :roundrobin
21
20
 
22
- AVAILABLE_WORKER_TYPES = [:read, :accept]
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(listener_type: @listener_type, host: @host, port: @port, backlog: nil)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RightSpeed
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -3,27 +3,27 @@ require_relative 'base'
3
3
  module RightSpeed
4
4
  module Worker
5
5
  class Accepter < Base
6
- def configure(sock)
7
- @sock = sock
8
- end
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
- data = conn.read
15
- # TODO: process it
16
- logger.info "[read|#{id}] Data: #{data}"
17
- conn.write "200 OK"
18
- ensure
19
- conn.close rescue nil
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
@@ -3,7 +3,7 @@ require_relative "../logger"
3
3
 
4
4
  module RightSpeed
5
5
  module Worker
6
- class Reader < Base
6
+ class RoundRobin < Base
7
7
  def run
8
8
  @ractor = Ractor.new(@id, @handler) do |id, handler|
9
9
  logger = RightSpeed.logger
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.1.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/reader.rb
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: