right_speed 0.1.0 → 0.2.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.
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: