grpc_kit 0.1.4 → 0.1.5

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: 7415d182781136999b2e1630ef9dff77a61196225b4207e9d9535a2b566fceee
4
- data.tar.gz: 9836c54ed198740c4a75e90b957a641528b06333a30314c03441b9dce35f8731
3
+ metadata.gz: 5cd6355c75605e749f1590fa3f3f3346f5d77288753df21cd104c9af78d22f5f
4
+ data.tar.gz: 3e6d0c8606b0476edaca281b510b1c8d1f047df7accc479e2b1e569567869497
5
5
  SHA512:
6
- metadata.gz: '083b685dd4f7966156a1ac41eab594ace1664c813105e1204c521ce85e727945170ef1c701097ee6f93467407b13d08ea649d6da5f86af54ebd60ca1e373336e'
7
- data.tar.gz: 505029bdcbfb035cb41b10ed845cec6ce27eae2ff350a27f49cb8a3b2cb4afeca92b8e5b9a7bc70e7dc0e2e8bde9d88591cc8899a871d81d2665c5c8141a1d14
6
+ metadata.gz: bde53f9753f23d273ed4b211c63f2e4727e88c8d67b9b330add41c9bec5b82ebbe884c551c496c5fcdca57252e36d58660ac5a70f31ecd8cb84f00258a6972f9
7
+ data.tar.gz: 75d4b9b74c7cc99111c348c754c79cda00295745485ee6d28a4e0aa492bc28bcc6303dd04bbd95320a483a3bc8559840c79be09621eabb04bfb853b7e7be9760
data/README.md CHANGED
@@ -47,7 +47,8 @@ end
47
47
  ##### Client
48
48
 
49
49
  ```ruby
50
- stub = Helloworld::Greeter::Stub.new('localhost', 50051)
50
+ sock = TCPSocket.new('localhost', 50051)
51
+ stub = Helloworld::Greeter::Stub.new(sock)
51
52
  message = stub.say_hello(Helloworld::HelloRequest.new(name: 'your name')).message
52
53
  puts message
53
54
  ```
data/TODO.md CHANGED
@@ -13,8 +13,8 @@
13
13
  ### server streamer
14
14
 
15
15
  - [x] recv/send msg
16
- - [ ] metadata (client)
17
- - [ ] metadata (server)
16
+ - [x] metadata (client)
17
+ - [x] metadata (server)
18
18
  - [x] interceptor (client)
19
19
  - [x] interceptor (server)
20
20
  - [ ] deadline (client)
@@ -23,8 +23,8 @@
23
23
  ### client streamer
24
24
 
25
25
  - [x] recv/send msg
26
- - [ ] metadata (client)
27
- - [ ] metadata (server)
26
+ - [x] metadata (client)
27
+ - [x] metadata (server)
28
28
  - [x] interceptor (client)
29
29
  - [x] interceptor (server)
30
30
  - [ ] deadline (client)
@@ -62,3 +62,10 @@
62
62
  - [ ] add server request spec
63
63
  - [ ] add client request spec
64
64
  - [ ] handle RST FRAME
65
+
66
+ ## bugs
67
+
68
+ - [x] status_check is invoked twice
69
+ - [x] undefined local variable or method `finish' for #<GrpcKit::Sessions::ClientSession:0x00007f9ae3abf970> (NameError)
70
+ - [x] clients don't use same object even if thier connections alive
71
+
data/grpc_kit.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_dependency 'ds9', '~> 1.2.0'
25
+ spec.add_dependency 'ds9', '~> 1.2.1'
26
26
  spec.add_dependency 'google-protobuf', '~> 3.6.1'
27
27
  spec.add_dependency 'googleapis-common-protos-types', '~> 1.0.2'
28
28
 
@@ -9,10 +9,10 @@ module GrpcKit
9
9
  attr_reader :metadata
10
10
  alias outgoing_metadata metadata
11
11
 
12
- def send_msg(data, timeout: nil, metadata: {}, last: false)
12
+ def send_msg(data, timeout: nil, last: false)
13
13
  raise 'No method error' if @restrict
14
14
 
15
- @stream.send_msg(data, last: last, timeout: timeout, metadata: metadata)
15
+ @stream.send_msg(data, last: last, timeout: timeout, metadata: outgoing_metadata)
16
16
  end
17
17
 
18
18
  def recv(last: false)
@@ -9,10 +9,10 @@ module GrpcKit
9
9
  attr_reader :metadata
10
10
  alias outgoing_metadata metadata
11
11
 
12
- def send_msg(data, timeout: nil, metadata: {}, last: false)
12
+ def send_msg(data, timeout: nil, last: false)
13
13
  raise 'No method error' if @restrict
14
14
 
15
- @stream.send_msg(data, last: last, timeout: timeout, metadata: metadata)
15
+ @stream.send_msg(data, last: last, timeout: timeout, metadata: outgoing_metadata)
16
16
  end
17
17
 
18
18
  def recv(last: false)
@@ -9,10 +9,10 @@ module GrpcKit
9
9
  attr_reader :metadata
10
10
  alias outgoing_metadata metadata
11
11
 
12
- def send_msg(data, timeout: nil, metadata: {}, last: false)
12
+ def send_msg(data, timeout: nil, last: false)
13
13
  raise 'No method error' if @restrict
14
14
 
15
- @stream.send_msg(data, last: last, timeout: timeout, metadata: metadata)
15
+ @stream.send_msg(data, last: last, timeout: timeout, metadata: outgoing_metadata)
16
16
  end
17
17
 
18
18
  def recv(last: false)
@@ -19,7 +19,14 @@ module GrpcKit
19
19
  def send_msg(data, last: false)
20
20
  raise 'No method error' if @restrict
21
21
 
22
- @stream.send_msg(data, @protobuf, last: last, limit_size: @config.max_send_message_size)
22
+ @stream.send_msg(
23
+ data,
24
+ @protobuf,
25
+ last: last,
26
+ initial_metadata: @outgoing_initial_metadata,
27
+ trailing_metadata: @outgoing_trailing_metadata,
28
+ limit_size: @config.max_send_message_size,
29
+ )
23
30
  end
24
31
 
25
32
  def recv(last: false)
@@ -19,7 +19,14 @@ module GrpcKit
19
19
  def send_msg(data, last: false)
20
20
  raise 'No method error' if @restrict
21
21
 
22
- @stream.send_msg(data, @protobuf, last: last, limit_size: @config.max_send_message_size)
22
+ @stream.send_msg(
23
+ data,
24
+ @protobuf,
25
+ last: last,
26
+ initial_metadata: @outgoing_initial_metadata,
27
+ trailing_metadata: @outgoing_trailing_metadata,
28
+ limit_size: @config.max_send_message_size,
29
+ )
23
30
  end
24
31
 
25
32
  def recv(last: false)
@@ -19,7 +19,14 @@ module GrpcKit
19
19
  def send_msg(data, last: false)
20
20
  raise 'No method error' if @restrict
21
21
 
22
- @stream.send_msg(data, @protobuf, last: last, limit_size: @config.max_send_message_size)
22
+ @stream.send_msg(
23
+ data,
24
+ @protobuf,
25
+ last: last,
26
+ initial_metadata: @outgoing_initial_metadata,
27
+ trailing_metadata: @outgoing_trailing_metadata,
28
+ limit_size: @config.max_send_message_size,
29
+ )
23
30
  end
24
31
 
25
32
  def recv(last: false)
@@ -8,6 +8,9 @@ module GrpcKit
8
8
  Klass = Struct.new(:service_name)
9
9
  attr_reader :method
10
10
 
11
+ # @params stream [GrpcKit::Stream::ServerStream|GrpcKit::Stream::ClientStream]
12
+ # @params config [GrpcKit::MethodConfig]
13
+ # @params metadata [Hash]
11
14
  def initialize(stream:, config:, metadata:, timeout: nil)
12
15
  @config = config
13
16
  @metadata = metadata
@@ -2,9 +2,9 @@
2
2
 
3
3
  require 'grpc_kit/grpc_time'
4
4
  require 'grpc_kit/session/io'
5
- require 'grpc_kit/sessions/client_session'
6
- require 'grpc_kit/streams/client_stream'
7
- require 'grpc_kit/transports/client_transport'
5
+ require 'grpc_kit/session/client_session'
6
+ require 'grpc_kit/stream/client_stream'
7
+ require 'grpc_kit/transport/client_transport'
8
8
 
9
9
  module GrpcKit
10
10
  class Client
@@ -17,43 +17,46 @@ module GrpcKit
17
17
  addr = sock.addr
18
18
  "#{addr[3]}:#{addr[1]}"
19
19
  end
20
- @interceptors = interceptors
20
+
21
21
  @timeout = timeout && GrpcKit::GrpcTime.new(timeout)
22
+
23
+ build_rpcs(interceptors)
22
24
  end
23
25
 
24
26
  def request_response(rpc, request, opts = {})
25
27
  GrpcKit.logger.debug('Calling request_respose')
26
-
27
- rpc.config.interceptor.interceptors = @interceptors
28
28
  do_request(rpc, request, opts)
29
29
  end
30
30
 
31
31
  def client_streamer(rpc, opts = {})
32
32
  GrpcKit.logger.debug('Calling client_streamer')
33
- rpc.config.interceptor.interceptors = @interceptors
34
33
  do_request(rpc, nil, opts)
35
34
  end
36
35
 
37
36
  def server_streamer(rpc, request, opts = {})
38
37
  GrpcKit.logger.debug('Calling server_streamer')
39
- rpc.config.interceptor.interceptors = @interceptors
40
38
  do_request(rpc, request, opts)
41
39
  end
42
40
 
43
41
  def bidi_streamer(rpc, requests, opts = {})
44
- rpc.config.interceptor.interceptors = @interceptors
45
42
  GrpcKit.logger.debug('Calling bidi_streamer')
46
43
  end
47
44
 
48
45
  private
49
46
 
50
47
  def do_request(rpc, request, **opts)
51
- session = GrpcKit::Sessions::ClientSession.new(GrpcKit::Session::IO.new(@sock))
52
- session.submit_settings([])
53
-
54
- t = GrpcKit::Transports::ClientTransport.new(session)
55
- cs = GrpcKit::Streams::ClientStream.new(t, rpc.config, authority: @authority, timeout: @timeout)
48
+ t = GrpcKit::Transport::ClientTransport.new(session)
49
+ cs = GrpcKit::Stream::ClientStream.new(t, rpc.config, authority: @authority, timeout: @timeout)
56
50
  rpc.invoke(cs, request, opts.merge(timeout: @timeout))
57
51
  end
52
+
53
+ def session
54
+ @session ||=
55
+ begin
56
+ s = GrpcKit::Session::ClientSession.new(GrpcKit::Session::IO.new(@sock))
57
+ s.submit_settings([])
58
+ s
59
+ end
60
+ end
58
61
  end
59
62
  end
@@ -48,27 +48,40 @@ module GrpcKit
48
48
  end
49
49
 
50
50
  def rpc_stub_class
51
- rpc_descs_ = rpc_descs
51
+ rpc_descs_ = {}
52
+ rpc_descs.each_value do |rpc_desc|
53
+ rpc_descs_[rpc_desc.ruby_style_name] = rpc_desc
54
+ end
55
+
52
56
  Class.new(GrpcKit::Client) do
53
- rpc_descs_.each do |_, rpc_desc|
54
- method_name = rpc_desc.ruby_style_name
55
- rpc = rpc_desc.build_client
57
+ def initialize(*)
58
+ @rpcs = {}
59
+ super
60
+ end
61
+
62
+ define_method(:build_rpcs) do |interceptors|
63
+ rpc_descs_.each do |method_name, rpc_desc|
64
+ @rpcs[method_name] = rpc_desc.build_client(interceptors: interceptors)
65
+ end
66
+ end
67
+ private :build_rpcs
56
68
 
69
+ rpc_descs_.each do |method_name, rpc_desc|
57
70
  if rpc_desc.request_response?
58
71
  define_method(method_name) do |request, opts = {}|
59
- request_response(rpc, request, opts)
72
+ request_response(@rpcs.fetch(method_name), request, opts)
60
73
  end
61
74
  elsif rpc_desc.client_streamer?
62
75
  define_method(method_name) do |opts = {}|
63
- client_streamer(rpc, opts)
76
+ client_streamer(@rpcs.fetch(method_name), opts)
64
77
  end
65
78
  elsif rpc_desc.server_streamer?
66
79
  define_method(method_name) do |request, opts = {}|
67
- server_streamer(rpc, request, opts)
80
+ server_streamer(@rpcs.fetch(method_name), request, opts)
68
81
  end
69
82
  elsif rpc_desc.bidi_streamer?
70
83
  define_method(method_name) do |requests, opts = {}, &blk|
71
- bidi_streamer(rpc, requests, opts, &blk)
84
+ bidi_streamer(@rpcs.fetch(method_name), requests, opts, &blk)
72
85
  end
73
86
  else
74
87
  raise "unknown #{rpc_desc}"
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GrpcKit
4
4
  class GrpcTime
5
- MAX = 10**9-1
5
+ MAX = 10**9 - 1
6
6
 
7
7
  # @params value [String|Integer]
8
8
  def initialize(value)
@@ -67,12 +67,7 @@ module GrpcKit
67
67
  private
68
68
 
69
69
  def from_integer(value)
70
- if value < 0
71
- @value = MAX
72
- else
73
- @value = value
74
- end
75
-
70
+ @value = value < 0 ? MAX : value
76
71
  @unit = 'S'
77
72
  end
78
73
 
@@ -5,11 +5,8 @@ require 'grpc_kit/interceptors'
5
5
  module GrpcKit
6
6
  module Interceptors::Client
7
7
  class RequestResponse
8
- attr_writer :interceptors
9
-
10
- def initialize
11
- # Cant' get interceptor at definition time...
12
- @interceptors = nil
8
+ def initialize(interceptors)
9
+ @interceptors = interceptors
13
10
  end
14
11
 
15
12
  def intercept(request, call, metadata, &block)
@@ -4,11 +4,8 @@ module GrpcKit
4
4
  module Interceptors
5
5
  module Client
6
6
  class Streaming
7
- attr_writer :interceptors
8
-
9
- def initialize
10
- # Cant' get interceptor at definition time...
11
- @interceptors = nil
7
+ def initialize(interceptors)
8
+ @interceptors = interceptors
12
9
  end
13
10
 
14
11
  def intercept(call, metadata, &block)
@@ -46,14 +46,16 @@ module GrpcKit
46
46
  server.new(handler, config)
47
47
  end
48
48
 
49
- def build_client
49
+ def build_client(interceptors: [])
50
+ inter = interceptors.empty? ? nil : client_interceptor.new(interceptors)
51
+
50
52
  config = GrpcKit::MethodConfig.build_for_client(
51
53
  path: path,
52
54
  ruby_style_method_name: ruby_style_name,
53
55
  protobuf: client_protobuf,
54
56
  service_name: @server_name,
55
57
  method_name: @name,
56
- interceptor: client_interceptor.new,
58
+ interceptor: inter,
57
59
  )
58
60
  client.new(config)
59
61
  end
@@ -14,8 +14,10 @@ module GrpcKit
14
14
  stream: stream,
15
15
  )
16
16
 
17
- @config.interceptor.intercept(call, metadata) do |s|
18
- s
17
+ if @config.interceptor
18
+ @config.interceptor.intercept(call, metadata) { |s| s }
19
+ else
20
+ call
19
21
  end
20
22
  end
21
23
  end
@@ -14,16 +14,27 @@ module GrpcKit
14
14
  stream: stream,
15
15
  )
16
16
 
17
- @config.interceptor.intercept(request, call, call.metadata) do |r, c, _|
18
- if timeout
17
+ # TODO: DRY
18
+ if @config.interceptor && timeout
19
+ @config.interceptor.intercept(request, call, call.metadata) do |r, c, _|
19
20
  Timeout.timeout(timeout.to_f, GrpcKit::Errors::DeadlineExceeded) do
20
- call.send_msg(r, timeout: timeout.to_s, metadata: c.metadata, last: true)
21
+ call.send_msg(request, timeout: timeout.to_s, last: true)
21
22
  call.recv(last: true)
22
23
  end
23
- else
24
- call.send_msg(r, metadata: c.metadata, last: true)
24
+ end
25
+ elsif @config.interceptor && !timeout
26
+ @config.interceptor.intercept(request, call, call.metadata) do |r, c, _|
27
+ call.send_msg(request, last: true)
28
+ call.recv(last: true)
29
+ end
30
+ elsif !@config.interceptor && timeout
31
+ Timeout.timeout(timeout.to_f, GrpcKit::Errors::DeadlineExceeded) do
32
+ call.send_msg(request, timeout: timeout.to_s, last: true)
25
33
  call.recv(last: true)
26
34
  end
35
+ else
36
+ call.send_msg(request, last: true)
37
+ call.recv(last: true)
27
38
  end
28
39
  end
29
40
  end
@@ -9,9 +9,14 @@ module GrpcKit
9
9
  def invoke(stream, request, metadata: {}, timeout: nil)
10
10
  call = GrpcKit::Calls::Client::ServerStreamer.new(metadata: metadata, config: @config, timeout: timeout, stream: stream)
11
11
 
12
- @config.interceptor.intercept(call, metadata) do |c, m|
13
- c.send_msg(request, metadata: m, last: true)
14
- c
12
+ if @config.interceptor
13
+ @config.interceptor.intercept(call, metadata) do |c, m|
14
+ c.send_msg(request, last: true)
15
+ c
16
+ end
17
+ else
18
+ call.send_msg(request, last: true)
19
+ call
15
20
  end
16
21
  end
17
22
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'grpc_kit/session/io'
4
- require 'grpc_kit/sessions/server_session'
4
+ require 'grpc_kit/session/server_session'
5
5
 
6
6
  module GrpcKit
7
7
  class Server
@@ -10,6 +10,8 @@ module GrpcKit
10
10
  @rpc_descs = {}
11
11
  @interceptors = interceptors
12
12
  @mutex = Mutex.new
13
+ @stopping = false
14
+ @max_graceful_wait = 60
13
15
 
14
16
  GrpcKit.logger.debug("Launched grpc_kit(v#{GrpcKit::VERSION})")
15
17
  end
@@ -26,23 +28,51 @@ module GrpcKit
26
28
  end
27
29
 
28
30
  def run(conn)
31
+ raise 'Stopping server' if @stopping
32
+
29
33
  establish_session(conn) do |s|
30
34
  s.submit_settings([])
31
35
  s.start
32
36
  end
33
37
  end
34
38
 
35
- def shutdown
36
- GrpcKit.logger.debug('Shutdown grpc_kit')
39
+ def wait_shutdown
40
+ loop do
41
+ return if @stopping
42
+
43
+ sleep 1
44
+ end
45
+ end
37
46
 
38
- @mutex.synchronize do
39
- @sessions.each(&:finish)
47
+ def force_shutdown
48
+ # expected to be called in trap context
49
+ Thread.new do
50
+ @mutex.synchronize do
51
+ GrpcKit.logger.debug('force shutdown')
52
+ @stopping = true
53
+ @sessions.each(&:shutdown)
54
+ end
40
55
  end
41
56
  end
42
57
 
43
58
  def graceful_shutdown
44
- @mutex.synchronize do
45
- @sessions.each(&:drain)
59
+ # expected to be called in trap context
60
+ Thread.new do
61
+ GrpcKit.logger.debug('graceful shutdown')
62
+ @mutex.synchronize { @sessions.each(&:drain) }
63
+ begin
64
+ Timeout.timeout(@max_graceful_wait) do
65
+ loop do
66
+ break if @mutex.synchronize { @sessions.empty? }
67
+
68
+ sleep 1
69
+ end
70
+ end
71
+ rescue Timeout::Error => _
72
+ GrpcKit.logger.debug('Max wait time expired')
73
+ end
74
+
75
+ @stopping = true
46
76
  end
47
77
  end
48
78
 
@@ -62,7 +92,7 @@ module GrpcKit
62
92
  private
63
93
 
64
94
  def establish_session(conn)
65
- session = GrpcKit::Sessions::ServerSession.new(GrpcKit::Session::IO.new(conn), self)
95
+ session = GrpcKit::Session::ServerSession.new(GrpcKit::Session::IO.new(conn), self)
66
96
  begin
67
97
  @mutex.synchronize { @sessions << session }
68
98
  yield(session)
@@ -5,10 +5,14 @@ require 'forwardable'
5
5
  require 'grpc_kit/session/stream'
6
6
 
7
7
  module GrpcKit
8
- module Sessions
8
+ module Session
9
9
  class ClientSession < DS9::Client
10
+ class ConnectionClosing < StandardError; end
11
+
10
12
  extend Forwardable
11
13
 
14
+ MAX_STREAM_ID = 2**31 - 1
15
+
12
16
  delegate %i[send_event recv_event] => :@io
13
17
 
14
18
  # @params io [GrpcKit::Session::IO]
@@ -18,16 +22,24 @@ module GrpcKit
18
22
  @io = io
19
23
  @streams = {}
20
24
  @opts = opts
25
+ @draining = false
26
+ @stop = false
27
+ @last_stream_id = 0
21
28
  end
22
29
 
23
30
  def send_request(data, headers)
24
- stream_id = submit_request(headers, data)
31
+ if @draining
32
+ raise ConnectionClosing, "You can't send new request. becuase this connection will shuting down"
33
+ end
34
+
35
+ stream_id = submit_request(headers, data).to_i
25
36
  stream = GrpcKit::Session::Stream.new(stream_id: stream_id, send_data: data)
26
37
  stream.stream_id = stream_id
27
38
  @streams[stream_id] = stream
28
39
  stream
29
40
  end
30
41
 
42
+ # @params stream_id [Integer]
31
43
  def start(stream_id)
32
44
  stream = @streams.fetch(stream_id)
33
45
 
@@ -38,13 +50,18 @@ module GrpcKit
38
50
 
39
51
  run_once
40
52
  end
41
- rescue Errno::ECONNRESET, IOError
42
- finish
53
+ rescue Errno::ECONNRESET, IOError => e
54
+ GrpcKit.logger.debug(e.message)
55
+ shutdown
43
56
  end
44
57
 
45
58
  def run_once
46
59
  return if @stop
47
60
 
61
+ if @draining && @drain_time < Time.now
62
+ raise 'trasport is closing'
63
+ end
64
+
48
65
  if want_read?
49
66
  do_read
50
67
  end
@@ -56,13 +73,18 @@ module GrpcKit
56
73
 
57
74
  private
58
75
 
76
+ def shutdown
77
+ @stop = true
78
+ @io.close
79
+ end
80
+
59
81
  def do_read
60
82
  receive
61
83
  rescue IOError => e
62
- finish
84
+ shutdown
63
85
  raise e
64
86
  rescue DS9::Exception => e
65
- finish
87
+ shutdown
66
88
  if DS9::ERR_EOF == e.code
67
89
  @peer_shutdowned = true
68
90
  return
@@ -93,9 +115,8 @@ module GrpcKit
93
115
  if frame.end_stream?
94
116
  stream.close_remote
95
117
  end
96
-
97
- # when DS9::Frames::Goaway
98
- # when DS9::Frames::RstStream
118
+ when DS9::Frames::Goaway
119
+ handle_goaway(frame)
99
120
  end
100
121
 
101
122
  true
@@ -154,6 +175,25 @@ module GrpcKit
154
175
  # # for nghttp2_session_callbacks_set_on_invalid_frame_recv_callback
155
176
  # def on_invalid_frame_recv(frame, error_code)
156
177
  # end
178
+
179
+ def handle_goaway(frame)
180
+ # shutdown notice
181
+ last_stream_id = frame.last_stream_id
182
+ if last_stream_id == MAX_STREAM_ID && frame.error_code == DS9::NO_ERROR
183
+ @draining = true
184
+ @drain_time = Time.now + 10 # XXX
185
+ @streams.each_value(&:drain)
186
+ end
187
+
188
+ @last_stream_id = last_stream_id
189
+ @streams.each do |id, stream|
190
+ if id > last_stream_id
191
+ stream.close
192
+ end
193
+ end
194
+
195
+ shutdown if @streams.empty?
196
+ end
157
197
  end
158
198
  end
159
199
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ds9'
4
+
5
+ module GrpcKit
6
+ module Session
7
+ class DrainController
8
+ def initialize
9
+ @sent_shutdown_notice = false
10
+ @goaway_sent = false
11
+ @after_one_rtt = false
12
+ # @sent_ping = false
13
+ end
14
+
15
+ def recv_ping_ack
16
+ @after_one_rtt = true
17
+ end
18
+
19
+ def call(session)
20
+ if @goaway_sent
21
+ # session.shutdown
22
+ elsif @sent_shutdown_notice && @after_one_rtt
23
+ session.submit_goaway(DS9::NO_ERROR, session.last_proc_stream_id)
24
+ @goaway_sent = true
25
+ # elsif @sent_shutdown_notice && !@after_one_rtt && !@sent_ping
26
+ # @sent_ping = true
27
+ elsif !@sent_shutdown_notice
28
+ session.submit_shutdown
29
+ @sent_shutdown_notice = true
30
+ session.submit_ping # wait for 1 RTT
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -23,10 +23,13 @@ module GrpcKit
23
23
  'grpc-message',
24
24
  'grpc-status',
25
25
  'grpc-status-details-bin',
26
+ 'grpc-accept-encoding',
26
27
  'te'
27
28
  ].freeze
28
29
 
29
- METADATA_ACCEPTABLE_HEADER = %w[authority user-agent].freeze
30
+ IGNORE_HEADERS = [':method', ':scheme'].freeze
31
+
32
+ METADATA_ACCEPTABLE_HEADER = %w[:authority user-agent].freeze
30
33
  def initialize
31
34
  super({}) # set metadata empty hash
32
35
  end
@@ -38,8 +41,7 @@ module GrpcKit
38
41
  when ':status'
39
42
  self.http_status = Integer(val)
40
43
  when 'content-type'
41
- # TODO
42
- metadata[key] = val
44
+ # self.grpc_encoding = val
43
45
  when 'grpc-encoding'
44
46
  self.grpc_encoding = val
45
47
  when 'grpc-status'
@@ -52,6 +54,10 @@ module GrpcKit
52
54
  # TODO
53
55
  GrpcKit.logger.warn('grpc-status-details-bin is unsupported header now')
54
56
  else
57
+ if IGNORE_HEADERS.include?(key)
58
+ return
59
+ end
60
+
55
61
  if RESERVED_HEADERS.include?(key) && !METADATA_ACCEPTABLE_HEADER.include?(key)
56
62
  return
57
63
  end
@@ -3,11 +3,12 @@
3
3
  require 'ds9'
4
4
  require 'forwardable'
5
5
  require 'grpc_kit/session/stream'
6
- require 'grpc_kit/streams/server_stream'
7
- require 'grpc_kit/transports/server_transport'
6
+ require 'grpc_kit/session/drain_controller'
7
+ require 'grpc_kit/stream/server_stream'
8
+ require 'grpc_kit/transport/server_transport'
8
9
 
9
10
  module GrpcKit
10
- module Sessions
11
+ module Session
11
12
  class ServerSession < DS9::Server
12
13
  extend Forwardable
13
14
 
@@ -24,11 +25,14 @@ module GrpcKit
24
25
  @dispatcher = dispatcher
25
26
  @peer_shutdowned = false
26
27
  @inflights = []
28
+ @drain = nil
27
29
  end
28
30
 
29
31
  def start
30
32
  @io.wait_readable
31
33
  loop do
34
+ break if @stop
35
+
32
36
  invoke
33
37
 
34
38
  if !want_read? && !want_write?
@@ -41,12 +45,23 @@ module GrpcKit
41
45
  end
42
46
  rescue Errno::ECONNRESET, IOError => e
43
47
  GrpcKit.logger.debug(e.message)
44
- finish
48
+ shutdown
49
+ ensure
50
+ GrpcKit.logger.debug('Finish server session')
45
51
  end
46
52
 
47
53
  def run_once
48
54
  return if @stop
49
55
 
56
+ if @drain
57
+ if @streams.empty?
58
+ shutdown
59
+ return
60
+ end
61
+
62
+ @drain.call(self)
63
+ end
64
+
50
65
  if want_read?
51
66
  do_read
52
67
  end
@@ -57,16 +72,14 @@ module GrpcKit
57
72
  end
58
73
 
59
74
  def drain
60
- # could be race condition
61
- @streams.each do |s|
62
- GrpcKit.logger.debug("#{s.stream_id} is draining")
63
- s.drain
64
- end
75
+ @drain ||= GrpcKit::Session::DrainController.new
65
76
  end
66
77
 
67
- def finish
78
+ def shutdown
68
79
  stop
69
80
  @io.close
81
+ rescue StandardError => e
82
+ GrpcKit.logger.debug(e)
70
83
  end
71
84
 
72
85
  private
@@ -77,8 +90,8 @@ module GrpcKit
77
90
 
78
91
  def invoke
79
92
  while (stream = @inflights.pop)
80
- t = GrpcKit::Transports::ServerTransport.new(self, stream)
81
- th = GrpcKit::Streams::ServerStream.new(t)
93
+ t = GrpcKit::Transport::ServerTransport.new(self, stream)
94
+ th = GrpcKit::Stream::ServerStream.new(t)
82
95
  @dispatcher.dispatch(stream.headers.path, th)
83
96
  end
84
97
  end
@@ -86,10 +99,10 @@ module GrpcKit
86
99
  def do_read
87
100
  receive
88
101
  rescue IOError => e
89
- finish
102
+ shutdown
90
103
  raise e
91
104
  rescue DS9::Exception => e
92
- finish
105
+ shutdown
93
106
  if DS9::ERR_EOF == e.code
94
107
  @peer_shutdowned = true
95
108
  return
@@ -136,7 +149,15 @@ module GrpcKit
136
149
  if frame.end_stream?
137
150
  stream.close_remote
138
151
  end
139
-
152
+ when DS9::Frames::Ping
153
+ if frame.ping_ack?
154
+ GrpcKit.logger.debug('ping ack is received')
155
+ # nghttp2 can't send any data once server sent actaul GoAway(not shutdown notice) frame.
156
+ # We want to send data in this case when ClientStreamer or BidiBstreamer which they are sending data in same stream
157
+ # So we have to wait to send actual GoAway frame untill timeout or something
158
+
159
+ # @drain.recv_ping_ack if @drain
160
+ end
140
161
  # when DS9::Frames::Goaway
141
162
  # when DS9::Frames::RstStream
142
163
  end
@@ -192,9 +213,13 @@ module GrpcKit
192
213
  def on_stream_close(stream_id, error_code)
193
214
  GrpcKit.logger.debug("on_stream_close stream_id=#{stream_id}, error_code=#{error_code}")
194
215
  stream = @streams.delete(stream_id)
195
- return unless stream
216
+ stream.close if stream
196
217
 
197
- stream.close
218
+ if @drain
219
+ if @streams.empty?
220
+ shutdown
221
+ end
222
+ end
198
223
  end
199
224
 
200
225
  # nghttp2_session_callbacks_set_on_data_chunk_recv_callback
@@ -3,9 +3,9 @@
3
3
  require 'grpc_kit/status_codes'
4
4
 
5
5
  module GrpcKit
6
- module Streams
6
+ module Stream
7
7
  class ClientStream
8
- # @params transport [GrpcKit::Transports::ClientTransport]
8
+ # @params transport [GrpcKit::Transport::ClientTransport]
9
9
  # @params config [GrpcKit::MethodConfig]
10
10
  # @params authority [String]
11
11
  def initialize(transport, config, authority:, timeout: nil)
@@ -15,7 +15,7 @@ module GrpcKit
15
15
  @authority = authority
16
16
  @timeout = timeout
17
17
 
18
- @sent_first_msg = false
18
+ @started = false
19
19
  end
20
20
 
21
21
  def send_msg(data, metadata: {}, timeout: nil, last: false)
@@ -31,15 +31,10 @@ module GrpcKit
31
31
  raise GrpcKit::Errors::ResourceExhausted, "Sending message is too large: send=#{req.bytesize}, max=#{limit_size}"
32
32
  end
33
33
 
34
- if @sent_first_msg
35
- # unless metadata.empty?
36
- # raise 'You can attach metadata at first send_msg' # XXX
37
- # end
34
+ if @started
38
35
  @transport.write_data(buf, last: last)
39
36
  else
40
- headers = build_headers(metadata: metadata)
41
- @transport.send_request(buf, headers, last: last)
42
- @sent_first_msg = true
37
+ start_request(buf, metadata: metadata, last: last)
43
38
  end
44
39
  end
45
40
 
@@ -59,7 +54,6 @@ module GrpcKit
59
54
  validate_if_request_start!
60
55
 
61
56
  @transport.close_and_flush
62
- check_status!
63
57
 
64
58
  data = []
65
59
  loop { data.push(do_recv) }
@@ -69,7 +63,7 @@ module GrpcKit
69
63
  private
70
64
 
71
65
  def validate_if_request_start!
72
- unless @sent_first_msg
66
+ unless @started
73
67
  raise 'You should call `send_msg` method to send data'
74
68
  end
75
69
  end
@@ -80,6 +74,8 @@ module GrpcKit
80
74
  if data.nil?
81
75
  check_status!
82
76
  raise StopIteration
77
+ elsif last
78
+ check_status!
83
79
  end
84
80
 
85
81
  compressed, size, buf = *data
@@ -107,39 +103,47 @@ module GrpcKit
107
103
  end
108
104
 
109
105
  def check_status!
110
- headers = @transport.recv_headers
111
-
112
- if headers.grpc_status != GrpcKit::StatusCodes::OK
113
- raise GrpcKit::Errors.from_status_code(headers.grpc_status, headers.status_message)
106
+ if status.code != GrpcKit::StatusCodes::OK
107
+ raise GrpcKit::Errors.from_status_code(status.code, status.msg)
114
108
  else
115
109
  GrpcKit.logger.debug('request is success')
116
110
  end
117
111
  end
118
112
 
119
- def build_headers(metadata: {}, **headers)
120
- # TODO: an order of Headers is important?
113
+ Status = Struct.new(:code, :msg, :metadata)
114
+
115
+ def status
116
+ @status ||=
117
+ begin
118
+ headers = @transport.recv_headers
119
+ Status.new(headers.grpc_status, headers.status_message, headers.metadata)
120
+ end
121
+ end
122
+
123
+ def start_request(buf = nil, last: nil, metadata: {}, timeout: @timeout, authority: @authority)
121
124
  hdrs = {
122
125
  ':method' => 'POST',
123
126
  ':scheme' => 'http',
124
127
  ':path' => @config.path,
125
- ':authority' => @authority,
126
- 'grpc-timeout' => @timeout,
128
+ ':authority' => authority,
129
+ 'grpc-timeout' => timeout,
127
130
  'te' => 'trailers',
128
131
  'content-type' => 'application/grpc',
129
132
  'user-agent' => "grpc-ruby/#{GrpcKit::VERSION} (grpc_kit)",
130
133
  'grpc-accept-encoding' => 'identity,deflate,gzip',
131
- }.merge(headers)
134
+ }
132
135
 
133
136
  metadata.each do |k, v|
134
137
  if k.start_with?('grpc-')
135
138
  # https://github.com/grpc/grpc/blob/ffac9d90b18cb076b1c952faa55ce4e049cbc9a6/doc/PROTOCOL-HTTP2.md
136
- GrpcKit.logger.info("metadata name wich starts with 'grpc-' is reserved for future GRPC")
139
+ GrpcKit.logger.info("metadata name wich starts with 'grpc-' is reserved for future GRPC metadata")
137
140
  else
138
141
  hdrs[k] = v
139
142
  end
140
143
  end
141
144
 
142
- hdrs.compact
145
+ @transport.start_request(buf, hdrs.compact, last: last)
146
+ @started = true
143
147
  end
144
148
  end
145
149
  end
@@ -3,12 +3,12 @@
3
3
  require 'grpc_kit/status_codes'
4
4
 
5
5
  module GrpcKit
6
- module Streams
6
+ module Stream
7
7
  class ServerStream
8
8
  # @params transport [GrpcKit::transports::ServerTransport]
9
9
  def initialize(transport)
10
10
  @transport = transport
11
- @sent_first_msg = false
11
+ @started = false
12
12
  end
13
13
 
14
14
  def invoke(rpc)
@@ -21,11 +21,7 @@ module GrpcKit
21
21
  send_status(status: GrpcKit::StatusCodes::UNKNOWN, msg: e.message, metadata: {})
22
22
  end
23
23
 
24
- def send_msg(data, protobuf, last: false, limit_size: nil)
25
- if last
26
- send_trailer # TODO: pass trailer metadata
27
- end
28
-
24
+ def send_msg(data, protobuf, last: false, limit_size: nil, initial_metadata: {}, trailing_metadata: {})
29
25
  buf =
30
26
  begin
31
27
  protobuf.encode(data)
@@ -37,11 +33,13 @@ module GrpcKit
37
33
  raise GrpcKit::Errors::ResourceExhausted, "Sending message is too large: send=#{req.bytesize}, max=#{limit_size}"
38
34
  end
39
35
 
40
- @transport.write_data(buf, last: last)
41
- return if @sent_first_msg
42
-
43
- send_response({})
44
- @sent_first_msg = true
36
+ if last
37
+ send_status(data: buf, metadata: trailing_metadata)
38
+ elsif @started
39
+ @transport.write_data(buf)
40
+ else
41
+ start_response(buf, metadata: initial_metadata)
42
+ end
45
43
  end
46
44
 
47
45
  def recv_msg(protobuf, last: false, limit_size: nil)
@@ -68,7 +66,7 @@ module GrpcKit
68
66
  begin
69
67
  protobuf.decode(buf)
70
68
  rescue ArgumentError => e
71
- raise GrpcKit::Errors::Internal, "Error while decoding in Server: #{e}"
69
+ raise GrpcKit::Errors::Internal, "Error while decoding in server: #{e}"
72
70
  end
73
71
  end
74
72
 
@@ -76,29 +74,31 @@ module GrpcKit
76
74
  loop { yield(recv) }
77
75
  end
78
76
 
79
- def send_status(status: GrpcKit::StatusCodes::INTERNAL, msg: nil, metadata: {})
80
- send_trailer(status: status, msg: msg, metadata: metadata)
81
- return if @sent_first_msg
77
+ def send_status(data: nil, status: GrpcKit::StatusCodes::OK, msg: nil, metadata: {})
78
+ @transport.write_data(data, last: true) if data
79
+ write_trailers(status, msg, metadata)
82
80
 
83
- send_response({})
84
- @sent_first_msg = true
81
+ start_response unless @started
85
82
  end
86
83
 
87
- def send_trailer(status: GrpcKit::StatusCodes::OK, msg: nil, metadata: {})
88
- trailer = metadata.dup
89
- trailer['grpc-status'] = status.to_s
90
- if msg
91
- trailer['grpc-message'] = msg
92
- end
84
+ private
85
+
86
+ def start_response(data = nil, metadata: {})
87
+ h = { ':status' => '200', 'content-type' => 'application/grpc' }
88
+ h['accept-encoding'] = 'identity'
93
89
 
94
- @transport.write_trailers_data(trailer)
90
+ @transport.write_data(data) if data
91
+ @transport.start_response(h.merge(metadata))
92
+ @started = true
95
93
  end
96
94
 
97
- def send_response(headers)
98
- h = { ':status' => '200', 'content-type' => 'application/grpc' }.merge(headers)
99
- h['accept-encoding'] = 'identity'
95
+ def write_trailers(status, msg, metadata)
96
+ trailers = { 'grpc-status' => status.to_s }
97
+ if msg
98
+ trailers['grpc-message'] = msg
99
+ end
100
100
 
101
- @transport.send_response(h)
101
+ @transport.write_trailers(trailers.merge(metadata))
102
102
  end
103
103
  end
104
104
  end
@@ -4,18 +4,18 @@ require 'grpc_kit/transport/packable'
4
4
  require 'grpc_kit/transport/send_buffer'
5
5
 
6
6
  module GrpcKit
7
- module Transports
7
+ module Transport
8
8
  class ClientTransport
9
9
  include GrpcKit::Transport::Packable
10
10
 
11
- # @params session [GrpcKit::Session::Server|GrpcKit::Session::Client]
11
+ # @params session [GrpcKit::Session::ClientSession]
12
12
  def initialize(session)
13
13
  @session = session
14
14
  @stream = nil # set later
15
15
  @deferred = false
16
16
  end
17
17
 
18
- def send_request(data, header, last: false)
18
+ def start_request(data, header, last: false)
19
19
  @stream = @session.send_request(GrpcKit::Transport::SendBuffer.new, header)
20
20
  write_data(data, last: last)
21
21
  end
@@ -3,11 +3,11 @@
3
3
  require 'grpc_kit/transport/packable'
4
4
 
5
5
  module GrpcKit
6
- module Transports
6
+ module Transport
7
7
  class ServerTransport
8
8
  include GrpcKit::Transport::Packable
9
9
 
10
- # @params session [GrpcKit::Sessions::ServerSession]
10
+ # @params session [GrpcKit::Session::ServerSession]
11
11
  # @params stream [GrpcKit::Session::Stream]
12
12
  def initialize(session, stream)
13
13
  @session = session
@@ -23,7 +23,7 @@ module GrpcKit
23
23
  end
24
24
  end
25
25
 
26
- def send_response(headers)
26
+ def start_response(headers)
27
27
  @session.submit_response(@stream.stream_id, headers)
28
28
  end
29
29
 
@@ -35,7 +35,7 @@ module GrpcKit
35
35
  unpack(read(last: last))
36
36
  end
37
37
 
38
- def write_trailers_data(trailer)
38
+ def write_trailers(trailer)
39
39
  @stream.write_trailers_data(trailer)
40
40
  @stream.end_write
41
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrpcKit
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.5'
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.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - ganmacs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-30 00:00:00.000000000 Z
11
+ date: 2018-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ds9
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2.0
19
+ version: 1.2.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2.0
26
+ version: 1.2.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: google-protobuf
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -208,19 +208,20 @@ files:
208
208
  - lib/grpc_kit/rpcs/server_server_streamer.rb
209
209
  - lib/grpc_kit/server.rb
210
210
  - lib/grpc_kit/session/buffer.rb
211
+ - lib/grpc_kit/session/client_session.rb
212
+ - lib/grpc_kit/session/drain_controller.rb
211
213
  - lib/grpc_kit/session/headers.rb
212
214
  - lib/grpc_kit/session/io.rb
215
+ - lib/grpc_kit/session/server_session.rb
213
216
  - lib/grpc_kit/session/stream.rb
214
217
  - lib/grpc_kit/session/stream_status.rb
215
- - lib/grpc_kit/sessions/client_session.rb
216
- - lib/grpc_kit/sessions/server_session.rb
217
218
  - lib/grpc_kit/status_codes.rb
218
- - lib/grpc_kit/streams/client_stream.rb
219
- - lib/grpc_kit/streams/server_stream.rb
219
+ - lib/grpc_kit/stream/client_stream.rb
220
+ - lib/grpc_kit/stream/server_stream.rb
221
+ - lib/grpc_kit/transport/client_transport.rb
220
222
  - lib/grpc_kit/transport/packable.rb
221
223
  - lib/grpc_kit/transport/send_buffer.rb
222
- - lib/grpc_kit/transports/client_transport.rb
223
- - lib/grpc_kit/transports/server_transport.rb
224
+ - lib/grpc_kit/transport/server_transport.rb
224
225
  - lib/grpc_kit/version.rb
225
226
  homepage: https://github.com/ganmacs/grpc_kit
226
227
  licenses: