grpc_kit 0.2.1 → 0.3.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: 669ad89bc39849c66aeea640ba77ac6204e7cb1c2034a77b4bd4b698830c071a
4
- data.tar.gz: fa4a14c0c2bf93837826b9477c126031849e95310f18f7633237d9a485688e56
3
+ metadata.gz: 24da7e7fc87c510f28152d959814121928623f6217da6d46148fa8a83f39446f
4
+ data.tar.gz: 792c2414ddffaf2e96abf6e8de8239cbf4a9cd1963675e9af9cf3af2434c7bf5
5
5
  SHA512:
6
- metadata.gz: c20fe45fbc01d727fdde2f70d1ebd53bcd2c89c03e9ef01e676a73344e7618aa7e32eaa07af71b10733cb79986fd1883647ba81a5e71b04779a8ded3c4bd8c4a
7
- data.tar.gz: be9fb063cd523f9c6c2a47a58584a930f431378a050af865106126b1784ace126291d226c088884c7bd479423d86076439189856e7831341a1b22d7465443043
6
+ metadata.gz: 8c7194e0e9043f0f984b226b13dee18ee3ff84168061f2c788a8deeeb8e8096c748c83a26635ef4241deaeb283e315e9ffb39d40a54065922daad0bb59152055
7
+ data.tar.gz: 0b502f3c1d0606c4bfe514eea8a0d250fbf7a34718d6f5926a9cbc7b0eaae296ba4f1a2f5a33eeebd50e665481927d6711377978d762184e29ec50a666b18b7e
@@ -0,0 +1,30 @@
1
+ module GrpcKit
2
+ class ControlQueue
3
+ def initialize
4
+ @event_stream = Queue.new
5
+ end
6
+
7
+ # Be nonblocking
8
+ def pop
9
+ if @event_stream.empty?
10
+ nil
11
+ else
12
+ @event_stream.pop(true)
13
+ end
14
+ rescue ThreadError => _
15
+ nil
16
+ end
17
+
18
+ def submit_response(id, headers)
19
+ @event_stream.push([:submit_response, id, headers])
20
+ end
21
+
22
+ def submit_headers(id, headers)
23
+ @event_stream.push([:submit_headers, id, headers])
24
+ end
25
+
26
+ def resume_data(id)
27
+ @event_stream.push([:resume_data, id])
28
+ end
29
+ end
30
+ end
@@ -7,9 +7,13 @@ module GrpcKit
7
7
  class Server
8
8
  # @param interceptors [Array<GrpcKit::Grpc::ServerInterceptor>] list of interceptors
9
9
  # @param shutdown_timeout [Integer] Number of seconds to wait for the server shutdown
10
- def initialize(interceptors: [], shutdown_timeout: 30)
10
+ # @param min_pool_size [Integer] A mininum thread pool size
11
+ # @param max_pool_size [Integer] A maximum thread pool size
12
+ def initialize(interceptors: [], shutdown_timeout: 30, min_pool_size: nil, max_pool_size: nil)
11
13
  @interceptors = interceptors
12
14
  @shutdown_timeout = shutdown_timeout
15
+ @min_pool_size = min_pool_size || GrpcKit::ThreadPool::DEFAULT_MIN
16
+ @max_pool_size = max_pool_size || GrpcKit::ThreadPool::DEFAULT_MAX
13
17
  @sessions = []
14
18
  @rpc_descs = {}
15
19
  @mutex = Mutex.new
@@ -68,16 +72,15 @@ module GrpcKit
68
72
  @mutex.synchronize { @sessions.each(&:drain) }
69
73
 
70
74
  end_time = Time.now + @shutdown_timeout
71
- until end_time < Time.now
72
- if @sessions.empty?
73
- return
75
+ loop do
76
+ if end_time < Time.now
77
+ GrpcKit.logger.error("Graceful shutdown is timeout (#{@shutdown_timeout}sec). Perform shutdown forceibly")
78
+ shutdown_sessions
79
+ break
80
+ elsif @sessions.empty?
81
+ break
74
82
  end
75
-
76
- sleep 1
77
83
  end
78
-
79
- GrpcKit.logger.error('Timeout graceful shutdown. perform force shutdown')
80
- shutdown_sessions
81
84
  end
82
85
  end
83
86
 
@@ -85,28 +88,35 @@ module GrpcKit
85
88
  @mutex.synchronize { @sessions.size }
86
89
  end
87
90
 
88
- # @param path [String] gRPC method path
89
- # @param stream [GrpcKit::Streams::ServerStream]
90
- # @return [void]
91
- def dispatch(path, stream)
91
+ private
92
+
93
+ def dispatch(stream, control_queue)
94
+ t = GrpcKit::Transport::ServerTransport.new(control_queue, stream)
95
+ ss = GrpcKit::Stream::ServerStream.new(t)
96
+ path = stream.headers.path
97
+
92
98
  rpc = @rpc_descs[path]
93
99
  unless rpc
94
100
  e = GrpcKit::Errors::Unimplemented.new(path)
95
- stream.send_status(status: e.code, msg: e.message)
101
+ ss.send_status(status: e.code, msg: e.message)
96
102
  return
97
103
  end
98
104
 
99
- stream.invoke(rpc)
105
+ ss.invoke(rpc)
100
106
  end
101
107
 
102
- private
108
+ def thread_pool
109
+ @thread_pool ||= GrpcKit::ThreadPool.new(min: @min_pool_size, max: @max_pool_size) do |task|
110
+ dispatch(task[0], task[1])
111
+ end
112
+ end
103
113
 
104
114
  def shutdown_sessions
105
115
  @mutex.synchronize { @sessions.each(&:shutdown) }
106
116
  end
107
117
 
108
118
  def establish_session(conn)
109
- session = GrpcKit::Session::ServerSession.new(GrpcKit::Session::IO.new(conn), self)
119
+ session = GrpcKit::Session::ServerSession.new(GrpcKit::Session::IO.new(conn), thread_pool)
110
120
  begin
111
121
  @mutex.synchronize { @sessions << session }
112
122
  yield(session)
@@ -6,32 +6,36 @@ module GrpcKit
6
6
  def initialize
7
7
  @buffer = +''.b
8
8
  @end = false
9
+ @mutex = Mutex.new
9
10
  end
10
11
 
11
12
  # @param data [String]
12
13
  # @return [void]
13
14
  def write(data)
14
- @buffer << data
15
+ @mutex.synchronize { @buffer << data }
15
16
  end
16
17
 
17
18
  # @param size [Integer,nil]
18
19
  # @param last [Boolean]
19
20
  # @return [String,nil]
20
21
  def read(size = nil, last: false)
21
- return nil if @buffer.empty?
22
+ buf = @mutex.synchronize do
23
+ return nil if @buffer.empty?
22
24
 
23
- end_read if last
24
-
25
- if size.nil? || @buffer.bytesize < size
26
- buf = @buffer
27
- @buffer = ''.b
28
- buf
29
- else
30
- @buffer.freeze
31
- rbuf = @buffer.byteslice(0, size)
32
- @buffer = @buffer.byteslice(size, @buffer.bytesize)
33
- rbuf
25
+ if size.nil? || @buffer.bytesize < size
26
+ buf = @buffer
27
+ @buffer = ''.b
28
+ buf
29
+ else
30
+ @buffer.freeze
31
+ rbuf = @buffer.byteslice(0, size)
32
+ @buffer = @buffer.byteslice(size, @buffer.bytesize)
33
+ rbuf
34
+ end
34
35
  end
36
+
37
+ end_read if last
38
+ buf
35
39
  end
36
40
 
37
41
  # @return [Boolean]
@@ -7,14 +7,15 @@ module GrpcKit
7
7
  @buffer = ''.b
8
8
  @end_write = false
9
9
  @deferred_read = false
10
+ @mutex = Mutex.new
10
11
  end
11
12
 
12
13
  # @param data [String]
13
14
  # @param last [Boolean]
14
15
  # @return [void]
15
16
  def write(data, last: false)
17
+ @mutex.synchronize { @buffer << data }
16
18
  end_write if last
17
- @buffer << data
18
19
  end
19
20
 
20
21
  # @return [Boolean]
@@ -32,11 +33,21 @@ module GrpcKit
32
33
  @end_write
33
34
  end
34
35
 
36
+ def empty?
37
+ @mutex.synchronize { @buffer.empty? }
38
+ end
39
+
35
40
  # @param size [Integer,nil]
36
41
  # @return [nil,DS9::ERR_DEFERRED,String]
37
42
  def read(size = nil)
38
- if @buffer.empty?
43
+ buf = do_read(size)
44
+ if buf.nil?
39
45
  if end_write?
46
+ # Call again because #write invokes `@buffer << data` before calling #end_write
47
+ if (buf = do_read(size))
48
+ return buf
49
+ end
50
+
40
51
  @deferred_read = false
41
52
  return nil # EOF
42
53
  end
@@ -45,15 +56,25 @@ module GrpcKit
45
56
  return DS9::ERR_DEFERRED
46
57
  end
47
58
 
48
- if size.nil? || @buffer.bytesize < size
49
- buf = @buffer
50
- @buffer = ''.b
51
- buf
52
- else
53
- @buffer.freeze
54
- rbuf = @buffer.byteslice(0, size)
55
- @buffer = @buffer.byteslice(size, @buffer.bytesize)
56
- rbuf
59
+ buf
60
+ end
61
+
62
+ private
63
+
64
+ def do_read(size = nil)
65
+ @mutex.synchronize do
66
+ if @buffer.empty?
67
+ nil
68
+ elsif size.nil? || @buffer.bytesize < size
69
+ buf = @buffer
70
+ @buffer = ''.b
71
+ buf
72
+ else
73
+ @buffer.freeze
74
+ rbuf = @buffer.byteslice(0, size)
75
+ @buffer = @buffer.byteslice(size, @buffer.bytesize)
76
+ rbuf
77
+ end
57
78
  end
58
79
  end
59
80
  end
@@ -2,11 +2,13 @@
2
2
 
3
3
  require 'ds9'
4
4
  require 'forwardable'
5
+ require 'grpc_kit/control_queue'
5
6
  require 'grpc_kit/session/stream'
6
7
  require 'grpc_kit/session/drain_controller'
7
8
  require 'grpc_kit/stream/server_stream'
8
- require 'grpc_kit/transport/server_transport'
9
9
  require 'grpc_kit/session/send_buffer'
10
+ require 'grpc_kit/transport/server_transport'
11
+ require 'grpc_kit/thread_pool'
10
12
 
11
13
  module GrpcKit
12
14
  module Session
@@ -16,16 +18,17 @@ module GrpcKit
16
18
  delegate %i[send_event recv_event] => :@io
17
19
 
18
20
  # @param io [GrpcKit::Session::IO]
19
- # @param dispatcher [GrpcKit::Server]
20
- def initialize(io, dispatcher)
21
+ # @param pool [GrpcKit::ThreadPool] Thread pool handling reqeusts
22
+ def initialize(io, pool)
21
23
  super() # initialize DS9::Session
22
24
 
23
25
  @io = io
24
26
  @streams = {}
25
27
  @stop = false
26
- @dispatcher = dispatcher
27
28
  @inflights = []
28
29
  @drain_controller = GrpcKit::Session::DrainController.new
30
+ @control_queue = GrpcKit::ControlQueue.new
31
+ @pool = pool
29
32
  end
30
33
 
31
34
  # @return [void]
@@ -101,10 +104,20 @@ module GrpcKit
101
104
  end
102
105
 
103
106
  def invoke
104
- while (stream = @inflights.pop)
105
- t = GrpcKit::Transport::ServerTransport.new(self, stream)
106
- th = GrpcKit::Stream::ServerStream.new(t)
107
- @dispatcher.dispatch(stream.headers.path, th)
107
+ while (event = @control_queue.pop)
108
+ case event[0]
109
+ when :submit_response
110
+ # piggybacked previous invokeing #submit_response?
111
+ if @streams[event[1]].pending_send_data.empty?
112
+ next
113
+ end
114
+
115
+ submit_response(event[1], event[2])
116
+ when :submit_headers
117
+ submit_headers(event[1], event[2])
118
+ when :resume_data
119
+ resume_data(event[1])
120
+ end
108
121
  end
109
122
  end
110
123
 
@@ -138,7 +151,16 @@ module GrpcKit
138
151
  end
139
152
  end
140
153
 
154
+ # nghttp2_session_callbacks_set_on_data_chunk_recv_callback
155
+ def on_data_chunk_recv(stream_id, data, _flags)
156
+ stream = @streams[stream_id]
157
+ if stream
158
+ stream.pending_recv_data.write(data)
159
+ end
160
+ end
161
+
141
162
  # nghttp2_session_callbacks_set_on_frame_recv_callback
163
+ # Note: called after ServerSession#on_data_chunk_recv
142
164
  def on_frame_recv(frame)
143
165
  GrpcKit.logger.debug("on_frame_recv #{frame}") # Too many call
144
166
 
@@ -152,7 +174,7 @@ module GrpcKit
152
174
 
153
175
  unless stream.inflight
154
176
  stream.inflight = true
155
- @inflights << stream
177
+ @pool.schedule([stream, @control_queue])
156
178
  end
157
179
  when DS9::Frames::Headers
158
180
  if frame.end_stream?
@@ -233,14 +255,6 @@ module GrpcKit
233
255
  end
234
256
  end
235
257
  end
236
-
237
- # nghttp2_session_callbacks_set_on_data_chunk_recv_callback
238
- def on_data_chunk_recv(stream_id, data, _flags)
239
- stream = @streams[stream_id]
240
- if stream
241
- stream.pending_recv_data.write(data)
242
- end
243
- end
244
258
  end
245
259
  end
246
260
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrpcKit
4
+ class ThreadPool
5
+ class AutoTrimmer
6
+ def initialize(pool, interval: 30)
7
+ @pool = pool
8
+ @interval = interval
9
+ @running = false
10
+ end
11
+
12
+ def start!
13
+ @running = true
14
+ @thread = Thread.new do
15
+ loop do
16
+ unless @running
17
+ GrpcKit.logger.debug('Stop AutoTrimer')
18
+ break
19
+ end
20
+ @pool.trim
21
+ sleep @interval
22
+ end
23
+ end
24
+ end
25
+
26
+ def stop
27
+ @running = false
28
+ @thread.wakeup
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_kit/thread_pool/auto_trimmer'
4
+
5
+ module GrpcKit
6
+ class ThreadPool
7
+ DEFAULT_MAX = 20
8
+ DEFAULT_MIN = 5
9
+
10
+ # @param min [Integer] A mininum thread pool size
11
+ # @param max [Integer] A maximum thread pool size
12
+ # @param interval [Integer] An interval time of calling #trim
13
+ def initialize(max: DEFAULT_MAX, min: DEFAULT_MIN, interval: 30, &block)
14
+ @max_pool_size = max
15
+ @min_pool_size = min
16
+ @shutdown = false
17
+ @tasks = Queue.new
18
+ unless max == min
19
+ @auto_trimmer = AutoTrimmer.new(self, interval: interval).tap(&:start!)
20
+ end
21
+
22
+ @spawned = 0
23
+ @workers = []
24
+ @mutex = Mutex.new
25
+ @block = block
26
+
27
+ @min_pool_size.times { spawn_thread }
28
+ end
29
+
30
+ # @param task [Object] task to schedule
31
+ def schedule(task, &block)
32
+ if task.nil?
33
+ return
34
+ end
35
+
36
+ if @shutdown
37
+ raise "scheduling new task isn't allowed during shutdown"
38
+ end
39
+
40
+ @tasks.push(block || task)
41
+
42
+ Thread.pass
43
+ if !@tasks.empty? && @mutex.synchronize { @spawned > @min_pool_size }
44
+ spawn_thread
45
+ end
46
+ end
47
+
48
+ def shutdown
49
+ @shutdown = true
50
+ @auto_trimmer.stop if @auto_trimmer
51
+ @waiting.times { @tasks.push(nil) }
52
+ end
53
+
54
+ def trim(force = false)
55
+ if (force || @tasks.empty?) && @mutex.synchronize { @spawned > @min_pool_size }
56
+ GrpcKit.logger.debug("Trim worker! Next worker size #{@spawned - 1}")
57
+ @tasks.push(nil)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def spawn_thread
64
+ @spawned += 1
65
+ worker = Thread.new(@spawned) do |i|
66
+ Thread.current.name = "grpc_kit worker thread #{i}"
67
+ GrpcKit.logger.debug("#{Thread.current.name} started")
68
+
69
+ loop do
70
+ if @shutdown
71
+ break
72
+ end
73
+
74
+ task = @tasks.pop
75
+ if task.nil?
76
+ break
77
+ end
78
+
79
+ begin
80
+ @block.call(task)
81
+ rescue Exception => e # rubocop:disable Lint/RescueException
82
+ GrpcKit.logger.error("An error occured on top level in worker #{Thread.current.name}: #{e.message} (#{e.class})\n #{Thread.current.backtrace.join("\n")}")
83
+ end
84
+ end
85
+
86
+ GrpcKit.logger.debug("worker thread #{Thread.current.name} is stopping")
87
+ @mutex.synchronize do
88
+ @spawned -= 1
89
+ @workers.delete(worker)
90
+ end
91
+ end
92
+
93
+ @workers.push(worker)
94
+ end
95
+ end
96
+ end
@@ -7,24 +7,23 @@ module GrpcKit
7
7
  class ServerTransport
8
8
  include GrpcKit::Transport::Packable
9
9
 
10
- # @param session [GrpcKit::Session::ServerSession]
10
+ # @param session [GrpcKit::ControlQueue]
11
11
  # @param stream [GrpcKit::Session::Stream]
12
- def initialize(session, stream)
13
- @session = session
12
+ def initialize(control_queue, stream)
13
+ @control_queue = control_queue
14
14
  @stream = stream
15
15
  end
16
16
 
17
17
  # @param headers [Hash<String, String>]
18
18
  # @return [void]
19
19
  def start_response(headers)
20
- @session.submit_response(@stream.stream_id, headers)
21
- send_data
20
+ @control_queue.submit_response(@stream.stream_id, headers)
22
21
  end
23
22
 
24
23
  # @param headers [Hash<String, String>]
25
24
  # @return [void]
26
25
  def submit_headers(headers)
27
- @session.submit_headers(@stream.stream_id, headers)
26
+ @control_queue.submit_headers(@stream.stream_id, headers)
28
27
  end
29
28
 
30
29
  # @param buf [String]
@@ -32,7 +31,6 @@ module GrpcKit
32
31
  # @return [void]
33
32
  def write_data(buf, last: false)
34
33
  @stream.write_send_data(pack(buf), last: last)
35
- send_data(last: last)
36
34
  end
37
35
 
38
36
  # @param last [Boolean]
@@ -45,7 +43,6 @@ module GrpcKit
45
43
  # @return [void]
46
44
  def write_trailers(trailer)
47
45
  @stream.write_trailers_data(trailer)
48
- send_data(last: true)
49
46
  end
50
47
 
51
48
  # @return [void]
@@ -63,29 +60,23 @@ module GrpcKit
63
60
  def recv_data(last: false)
64
61
  loop do
65
62
  data = @stream.read_recv_data(last: last)
66
- return data unless data.nil?
63
+ return data if data
67
64
 
68
65
  if @stream.close_remote?
69
- # it do not receive data which we need, it may receive invalid grpc-status
70
- unless @stream.end_read?
71
- return nil
72
- end
73
-
74
- return nil
66
+ # Call @stream.read_recv_data after checking @stream.close_remote?
67
+ # because of the order of nghttp2 callbacks which calls a callback receiving data before a callback receiving END_STREAM flag
68
+ data = @stream.read_recv_data(last: last)
69
+ return data
75
70
  end
76
-
77
- @session.run_once
78
71
  end
79
72
  end
80
73
 
81
- def send_data(last: false)
82
- if @stream.pending_send_data.need_resume?
83
- @session.resume_data(@stream.stream_id)
74
+ def send_data
75
+ unless @stream.pending_send_data.need_resume?
76
+ return
84
77
  end
85
78
 
86
- unless last
87
- @session.run_once
88
- end
79
+ @session.resume_data(@stream.stream_id)
89
80
  end
90
81
  end
91
82
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrpcKit
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grpc_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuta Iwama
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-01-08 00:00:00.000000000 Z
11
+ date: 2019-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ds9
@@ -183,6 +183,7 @@ files:
183
183
  - lib/grpc_kit/calls/server_server_streamer.rb
184
184
  - lib/grpc_kit/client.rb
185
185
  - lib/grpc_kit/codec.rb
186
+ - lib/grpc_kit/control_queue.rb
186
187
  - lib/grpc_kit/errors.rb
187
188
  - lib/grpc_kit/grpc/core.rb
188
189
  - lib/grpc_kit/grpc/dsl.rb
@@ -226,6 +227,8 @@ files:
226
227
  - lib/grpc_kit/status_codes.rb
227
228
  - lib/grpc_kit/stream/client_stream.rb
228
229
  - lib/grpc_kit/stream/server_stream.rb
230
+ - lib/grpc_kit/thread_pool.rb
231
+ - lib/grpc_kit/thread_pool/auto_trimmer.rb
229
232
  - lib/grpc_kit/transport/client_transport.rb
230
233
  - lib/grpc_kit/transport/packable.rb
231
234
  - lib/grpc_kit/transport/server_transport.rb