grpc_kit 0.2.1 → 0.3.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: 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