grpc_kit 0.3.6 → 0.5.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: d8fa450f3e7d2485f61e54edcbe6e664e63c5f4159c2f8c594600fa333a0eea0
4
- data.tar.gz: 5bd5154135572f48b683683aad531631f346562efd4841abfbf1ad0b3e3f4f5f
3
+ metadata.gz: f9713e72ba462451e689cd4aaa676994c96d591eaae9ddb9c4552f3d741431f7
4
+ data.tar.gz: 77af2186c14504d79c801ac216b7cb7576ddd8f516c7451f3e571bab3ade41c9
5
5
  SHA512:
6
- metadata.gz: cdd2a48377437970948468ac4ac9035dede0e79d8df6911416a44589d1e416b3470ac1b785784d8d37c80c89a0539859c736cbbf5952408b938fee9562543076
7
- data.tar.gz: 1d6b13647c1f0ec44cfd0828b5c88b7d42375378654f27b35702f9ab6c90ce3e06c3ee9747eee3884b90dfdd6af957f0c5820dfa38a7cdf206a5d37e49f78649
6
+ metadata.gz: 177555f8840c371e7d83cc67f6227a4043fe80fa06c4074b7ae5fcb9273af3f4115f06a4be94e4ea8491f6a5f606832114605d23611f27face8819bac937e2ab
7
+ data.tar.gz: 8a05c9f24bd762a845ed1a8f9652d108f3a281042c04387545c32954fd8c5bfa8a96e806229c8647dc4be6b3f75f334e0b1123ae7c25da06012ce325e629b7c4
@@ -0,0 +1,25 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ pull_request: {}
7
+
8
+ jobs:
9
+ ruby:
10
+ name: ruby
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ ruby_version:
15
+ - 2.5
16
+ - 2.6
17
+ - 2.7
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: '${{ matrix.ruby_version }}'
23
+ bundler-cache: true
24
+
25
+ - run: 'bundle exec rake'
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## v0.5.0 (2021-04-22)
4
+
5
+ - improve: Configurable max_receive_message_size and max_send_message_size ([#33](https://github.com/cookpad/grpc_kit/pull/33))
6
+
7
+ ## v0.4.0 (2020-10-23)
8
+
9
+ - bug: Fix Ruby 2.7 keyword argument separation warnings ([#27](https://github.com/cookpad/grpc_kit/pull/27))
10
+ - bug: HTTP/2 Trailer (grpc-status) might not be sent due to race condition ([#30](https://github.com/cookpad/grpc_kit/pull/30))
11
+ - improve: Reduce number of select(2) calls by adding pipe(2) to wake blocking threads ([#28](https://github.com/cookpad/grpc_kit/pull/28))
12
+ - improve: Improved performance when receiving streaming messages by blocking queue. ([#31](https://github.com/cookpad/grpc_kit/pull/31))
13
+
data/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # GrpcKit
2
2
 
3
- [![Build Status](https://travis-ci.org/ganmacs/grpc_kit.svg?branch=master)](https://travis-ci.org/ganmacs/grpc_kit)
3
+ [![Build Sttaus](https://github.com/cookpad/grpc_kit/workflows/ci/badge.svg)](https://github.com/cookpad/grpc_kit/actions)
4
4
  [![Gem Version](https://badge.fury.io/rb/grpc_kit.svg)](https://badge.fury.io/rb/grpc_kit)
5
5
 
6
- __UNDER DEVELOPMENT__
7
-
8
6
  A kit for creating [gRPC](https://grpc.io/) server/client in Ruby.
9
7
 
10
8
  ## Installation
@@ -29,7 +27,7 @@ $ gem install grpc_kit
29
27
 
30
28
  ## Usage
31
29
 
32
- More Details in [examples directory](https://github.com/ganmacs/grpc_kit/tree/master/examples).
30
+ More Details in [examples directory](https://github.com/cookpad/grpc_kit/tree/master/examples).
33
31
 
34
32
  ##### Server
35
33
 
@@ -61,11 +59,11 @@ $ bundle install
61
59
 
62
60
  ## Projects using grpc_kit
63
61
 
64
- * [griffin](https://github.com/ganmacs/griffin) Multi process gRPC server in Ruby
62
+ * [griffin](https://github.com/cookpad/griffin) Multi process gRPC server in Ruby
65
63
 
66
64
  ## Contributing
67
65
 
68
- Bug reports and pull requests are welcome on GitHub at https://github.com/ganmacs/grpc_kit.
66
+ Bug reports and pull requests are welcome on GitHub at https://github.com/cookpad/grpc_kit.
69
67
 
70
68
  ## License
71
69
 
data/grpc_kit.gemspec CHANGED
@@ -22,8 +22,8 @@ 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.3.3'
26
- spec.add_dependency 'google-protobuf', '>= 3.6.1'
25
+ spec.add_dependency 'ds9', '>= 1.4.0'
26
+ spec.add_dependency 'google-protobuf', '>= 3.7.0'
27
27
  spec.add_dependency 'googleapis-common-protos-types', '>= 1.0.2'
28
28
 
29
29
  spec.add_development_dependency 'bundler'
@@ -10,10 +10,13 @@ module GrpcKit
10
10
 
11
11
  alias outgoing_metadata metadata
12
12
 
13
- def initialize(*)
13
+ def initialize(**)
14
14
  super
15
- @mutex = Mutex.new
15
+ @recv_mutex = Mutex.new
16
+
16
17
  @send = false
18
+ @send_cv = Thread::ConditionVariable.new
19
+ @send_mutex = Mutex.new
17
20
  end
18
21
 
19
22
  # @param data [Object] request message
@@ -23,29 +26,21 @@ module GrpcKit
23
26
  raise "Upstream returns an error status: #{@reason}"
24
27
  end
25
28
 
26
- @mutex.synchronize do
29
+ @send_mutex.synchronize do
27
30
  @stream.send_msg(data, metadata: outgoing_metadata)
31
+ @send = true
32
+ @send_cv.broadcast
28
33
  end
29
-
30
- @send = true
31
34
  end
32
35
 
33
- # This method not is expected to be call in the main thread where #send_msg is called
34
- #
36
+ # Receive a message from peer. This method is not thread safe, never call from multiple threads at once.
35
37
  # @return [Object] response object
38
+ # @raise [StopIteration]
36
39
  def recv
37
- sleep 0.1 until @send
38
-
39
- loop do
40
- msg = @mutex.synchronize do
41
- @stream.recv_msg(blocking: false)
42
- end
43
-
44
- unless msg == :wait_readable
45
- return msg
46
- end
47
- end
40
+ @send_mutex.synchronize { @send_cv.wait(@send_mutex) until @send } unless @send
48
41
 
42
+ msg = @stream.recv_msg(blocking: true)
43
+ return msg if msg
49
44
  raise StopIteration
50
45
  rescue GrpcKit::Errors::BadStatus => e
51
46
  @reason = e
@@ -53,14 +48,16 @@ module GrpcKit
53
48
  end
54
49
 
55
50
  def close_and_send
56
- @mutex.synchronize do
51
+ @send_mutex.synchronize do
57
52
  @stream.close_and_send
58
53
  end
59
54
  end
60
55
 
61
56
  # @yieldparam response [Object] each response object of bidi streaming RPC
62
57
  def each
63
- loop { yield(recv) }
58
+ @recv_mutex.synchronize do
59
+ loop { yield(recv) }
60
+ end
64
61
  end
65
62
  end
66
63
  end
@@ -16,6 +16,7 @@ module GrpcKit
16
16
  @stream.send_msg(data, last: true, metadata: outgoing_metadata)
17
17
  end
18
18
 
19
+ # This method is not thread safe, never call from multiple threads at once.
19
20
  # @return [Object] response object
20
21
  def recv
21
22
  @stream.recv_msg
@@ -11,7 +11,7 @@ module GrpcKit
11
11
  attr_reader :outgoing_initial_metadata, :outgoing_trailing_metadata
12
12
  alias incoming_metadata metadata
13
13
 
14
- def initialize(*)
14
+ def initialize(**)
15
15
  super
16
16
 
17
17
  @outgoing_initial_metadata = {}
@@ -30,6 +30,7 @@ module GrpcKit
30
30
  )
31
31
  end
32
32
 
33
+ # This method is not thread safe, never call from multiple threads at once.
33
34
  # @return [Object] response object
34
35
  def recv
35
36
  @stream.recv_msg(@codec, limit_size: @config.max_receive_message_size)
@@ -11,7 +11,7 @@ module GrpcKit
11
11
  attr_reader :outgoing_initial_metadata, :outgoing_trailing_metadata
12
12
  alias incoming_metadata metadata
13
13
 
14
- def initialize(*)
14
+ def initialize(**)
15
15
  super
16
16
 
17
17
  @outgoing_initial_metadata = {}
@@ -31,6 +31,7 @@ module GrpcKit
31
31
  )
32
32
  end
33
33
 
34
+ # This method is not thread safe, never call from multiple threads at once.
34
35
  # @return [Object] response object
35
36
  def recv
36
37
  @stream.recv_msg(@codec, limit_size: @config.max_receive_message_size)
@@ -9,7 +9,7 @@ module GrpcKit
9
9
  attr_reader :outgoing_initial_metadata, :outgoing_trailing_metadata
10
10
  alias incoming_metadata metadata
11
11
 
12
- def initialize(*)
12
+ def initialize(**)
13
13
  super
14
14
 
15
15
  @outgoing_initial_metadata = {}
@@ -9,7 +9,7 @@ module GrpcKit
9
9
  attr_reader :outgoing_initial_metadata, :outgoing_trailing_metadata
10
10
  alias incoming_metadata metadata
11
11
 
12
- def initialize(*)
12
+ def initialize(**)
13
13
  super
14
14
 
15
15
  @outgoing_initial_metadata = {}
@@ -12,7 +12,9 @@ module GrpcKit
12
12
  # @param authority [nil, String]
13
13
  # @param interceptors [Array<GrpcKit::Grpc::ClientInterceptor>] list of interceptors
14
14
  # @param timeout [nil, Integer, String]
15
- def initialize(sock, authority: nil, interceptors: [], timeout: nil)
15
+ # @param max_receive_message_size [Integer, nil] Specify the default maximum size of inbound message in bytes. Default to 4MB.
16
+ # @param max_send_message_size [Integer, nil] Specify the default maximum size of outbound message in bytes. Default to 4MB.
17
+ def initialize(sock, authority: nil, interceptors: [], timeout: nil, max_receive_message_size: nil, max_send_message_size: nil)
16
18
  @sock = sock
17
19
  @authority =
18
20
  if authority
@@ -24,38 +26,43 @@ module GrpcKit
24
26
 
25
27
  @timeout = timeout && GrpcKit::GrpcTime.new(timeout)
26
28
 
27
- build_rpcs(interceptors)
29
+ # Defined at GrpcKit::Grpc::Dsl#.rpc_stub_class
30
+ build_rpcs(
31
+ interceptors,
32
+ max_receive_message_size: max_receive_message_size,
33
+ max_send_message_size: max_send_message_size,
34
+ )
28
35
  end
29
36
 
30
37
  # @param rpc [GrpcKit::Rpcs::Client::RequestResponse]
31
38
  # @param request [Object]
32
39
  # @param opts [Hash]
33
- def request_response(rpc, request, opts = {})
40
+ def request_response(rpc, request, **opts)
34
41
  GrpcKit.logger.debug('Calling request_respose')
35
- do_request(rpc, request, opts)
42
+ do_request(rpc, request, **opts)
36
43
  end
37
44
 
38
45
  # @param rpc [GrpcKit::Rpcs::Client::ClientStreamer]
39
46
  # @param opts [Hash]
40
- def client_streamer(rpc, opts = {})
47
+ def client_streamer(rpc, **opts)
41
48
  GrpcKit.logger.debug('Calling client_streamer')
42
- do_request(rpc, nil, opts)
49
+ do_request(rpc, nil, **opts)
43
50
  end
44
51
 
45
52
  # @param rpc [GrpcKit::Rpcs::Client::ServerStreamer]
46
53
  # @param request [Object]
47
54
  # @param opts [Hash]
48
- def server_streamer(rpc, request, opts = {})
55
+ def server_streamer(rpc, request, **opts)
49
56
  GrpcKit.logger.debug('Calling server_streamer')
50
- do_request(rpc, request, opts)
57
+ do_request(rpc, request, **opts)
51
58
  end
52
59
 
53
60
  # @param rpc [GrpcKit::Rpcs::Client::ServerStreamer]
54
61
  # @param _requests [Object] it's for compatibility, no use
55
62
  # @param opts [Hash]
56
- def bidi_streamer(rpc, _requests, opts = {})
63
+ def bidi_streamer(rpc, _requests, **opts)
57
64
  GrpcKit.logger.debug('Calling bidi_streamer')
58
- do_request(rpc, nil, opts)
65
+ do_request(rpc, nil, **opts)
59
66
  end
60
67
 
61
68
  private
@@ -7,6 +7,8 @@ require 'grpc_kit/grpc/stream'
7
7
 
8
8
  module GrpcKit
9
9
  module Grpc
10
+ # Methods under GrpcKit::Grpc::Dsl is available for classes include GRPC::GenericService.
11
+ # See also: GrpcKit::Grpc::GenericService
10
12
  module Dsl
11
13
  # @param value [String]
12
14
  attr_accessor :service_name
@@ -68,34 +70,34 @@ module GrpcKit
68
70
  end
69
71
 
70
72
  Class.new(GrpcKit::Client) do
71
- def initialize(*)
73
+ def initialize(*, **)
72
74
  @rpcs = {}
73
75
  super
74
76
  end
75
77
 
76
- define_method(:build_rpcs) do |interceptors|
78
+ define_method(:build_rpcs) do |interceptors, **options|
77
79
  rpc_descs_.each do |method_name, rpc_desc|
78
- @rpcs[method_name] = rpc_desc.build_client(interceptors: interceptors)
80
+ @rpcs[method_name] = rpc_desc.build_client(interceptors: interceptors, **options)
79
81
  end
80
82
  end
81
83
  private :build_rpcs
82
84
 
83
85
  rpc_descs_.each do |method_name, rpc_desc|
84
86
  if rpc_desc.request_response?
85
- define_method(method_name) do |request, opts = {}|
86
- request_response(@rpcs.fetch(method_name), request, opts)
87
+ define_method(method_name) do |request, **opts|
88
+ request_response(@rpcs.fetch(method_name), request, **opts)
87
89
  end
88
90
  elsif rpc_desc.client_streamer?
89
- define_method(method_name) do |opts = {}|
90
- client_streamer(@rpcs.fetch(method_name), opts)
91
+ define_method(method_name) do |**opts|
92
+ client_streamer(@rpcs.fetch(method_name), **opts)
91
93
  end
92
94
  elsif rpc_desc.server_streamer?
93
- define_method(method_name) do |request, opts = {}|
94
- server_streamer(@rpcs.fetch(method_name), request, opts)
95
+ define_method(method_name) do |request, **opts|
96
+ server_streamer(@rpcs.fetch(method_name), request, **opts)
95
97
  end
96
98
  elsif rpc_desc.bidi_streamer?
97
- define_method(method_name) do |requests, opts = {}, &blk|
98
- bidi_streamer(@rpcs.fetch(method_name), requests, opts, &blk)
99
+ define_method(method_name) do |requests, **opts, &blk|
100
+ bidi_streamer(@rpcs.fetch(method_name), requests, **opts, &blk)
99
101
  end
100
102
  else
101
103
  raise "unknown #{rpc_desc}"
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrpcKit
4
+ MAX_SERVER_RECEIVE_MESSAGE_SIZE = 1024 * 1024 * 4
5
+ MAX_SERVER_SEND_MESSAGE_SIZE = 1024 * 1024 * 4
6
+ MAX_CLIENT_RECEIVE_MESSAGE_SIZE = 1024 * 1024 * 4
7
+ MAX_CLIENT_SEND_MESSAGE_SIZE = 1024 * 1024 * 4
8
+
4
9
  MethodConfig = Struct.new(
5
10
  :path,
6
11
  :ruby_style_method_name,
@@ -12,11 +17,6 @@ module GrpcKit
12
17
  :max_send_message_size,
13
18
  :compressor_type,
14
19
  ) do
15
- MAX_SERVER_RECEIVE_MESSAGE_SIZE = 1024 * 1024 * 4
16
- MAX_SERVER_SEND_MESSAGE_SIZE = 1024 * 1024 * 4
17
- MAX_CLIENT_RECEIVE_MESSAGE_SIZE = 1024 * 1024 * 4
18
- MAX_CLIENT_SEND_MESSAGE_SIZE = 1024 * 1024 * 4
19
-
20
20
  def self.build_for_server(
21
21
  path:, ruby_style_method_name:, codec:, service_name:, method_name:, interceptor:,
22
22
  max_receive_message_size: MAX_SERVER_RECEIVE_MESSAGE_SIZE, max_send_message_size: MAX_SERVER_SEND_MESSAGE_SIZE, compressor_type: ''
@@ -38,10 +38,21 @@ module GrpcKit
38
38
  @service_name = service_name
39
39
  end
40
40
 
41
+ # @return [String]
42
+ attr_reader :name, :service_name
43
+
44
+ # @return [Class, GrpcKit::Grpc::Stream]
45
+ attr_reader :marshal, :unmarshal
46
+
47
+ # @return [Symbol]
48
+ attr_reader :marshal_method, :unmarshal_method
49
+
41
50
  # @param handler [GrpcKit::Grpc::GenericService]
42
51
  # @param interceptors [Array<GrpcKit::Grpc::ServerInterceptor>]
52
+ # @param max_receive_message_size [Integer, nil]
53
+ # @param max_send_message_size [Integer, nil]
43
54
  # @return [#invoke] Server version of rpc class
44
- def build_server(handler, interceptors: [])
55
+ def build_server(handler, interceptors: [], max_send_message_size: nil, max_receive_message_size: nil)
45
56
  inter = interceptors.empty? ? nil : server_interceptor.new(interceptors)
46
57
 
47
58
  config = GrpcKit::MethodConfig.build_for_server(
@@ -51,13 +62,17 @@ module GrpcKit
51
62
  service_name: @service_name,
52
63
  method_name: @name,
53
64
  interceptor: inter,
65
+ max_receive_message_size: max_receive_message_size || GrpcKit::MAX_SERVER_RECEIVE_MESSAGE_SIZE,
66
+ max_send_message_size: max_send_message_size || GrpcKit::MAX_SERVER_SEND_MESSAGE_SIZE,
54
67
  )
55
68
  server.new(handler, config)
56
69
  end
57
70
 
58
71
  # @param interceptors [Array<GrpcKit::Grpc::ClientInterceptor>]
72
+ # @param max_receive_message_size [Integer, nil]
73
+ # @param max_send_message_size [Integer, nil]
59
74
  # @return [#invoke] Client version of rpc class
60
- def build_client(interceptors: [])
75
+ def build_client(interceptors: [], max_send_message_size: nil, max_receive_message_size: nil)
61
76
  inter = interceptors.empty? ? nil : client_interceptor.new(interceptors)
62
77
 
63
78
  config = GrpcKit::MethodConfig.build_for_client(
@@ -67,6 +82,8 @@ module GrpcKit
67
82
  service_name: @service_name,
68
83
  method_name: @name,
69
84
  interceptor: inter,
85
+ max_receive_message_size: max_receive_message_size || GrpcKit::MAX_CLIENT_RECEIVE_MESSAGE_SIZE,
86
+ max_send_message_size: max_send_message_size || GrpcKit::MAX_CLIENT_SEND_MESSAGE_SIZE,
70
87
  )
71
88
  client.new(config)
72
89
  end
data/lib/grpc_kit/rpcs.rb CHANGED
@@ -19,6 +19,9 @@ module GrpcKit
19
19
  end
20
20
 
21
21
  class ServerRpc
22
+ # @return [GrpcKit::MethodConfig]
23
+ attr_reader :config
24
+
22
25
  # @param handler [GrpcKit::Grpc::GenericService]
23
26
  # @param config [GrpcKit::MethodConfig]
24
27
  def initialize(handler, config)
@@ -10,15 +10,20 @@ module GrpcKit
10
10
  # @param shutdown_timeout [Integer] Number of seconds to wait for the server shutdown
11
11
  # @param min_pool_size [Integer] A mininum thread pool size
12
12
  # @param max_pool_size [Integer] A maximum thread pool size
13
- def initialize(interceptors: [], shutdown_timeout: 30, min_pool_size: nil, max_pool_size: nil)
13
+ # @param max_receive_message_size [Integer, nil] Specify the maximum size of inbound message in bytes. Default to 4MB.
14
+ # @param max_send_message_size [Integer, nil] Specify the maximum size of outbound message in bytes. Default to 4MB.
15
+ def initialize(interceptors: [], shutdown_timeout: 30, min_pool_size: nil, max_pool_size: nil, settings: [], max_receive_message_size: nil, max_send_message_size: nil)
14
16
  @interceptors = interceptors
15
17
  @shutdown_timeout = shutdown_timeout
16
18
  @min_pool_size = min_pool_size || GrpcKit::RpcDispatcher::DEFAULT_MIN
17
19
  @max_pool_size = max_pool_size || GrpcKit::RpcDispatcher::DEFAULT_MAX
20
+ @max_receive_message_size = max_receive_message_size
21
+ @max_send_message_size = max_send_message_size
18
22
  @sessions = []
19
23
  @rpc_descs = {}
20
24
  @mutex = Mutex.new
21
25
  @stopping = false
26
+ @settings = settings
22
27
 
23
28
  GrpcKit.logger.debug("Launched grpc_kit(v#{GrpcKit::VERSION})")
24
29
  end
@@ -36,8 +41,12 @@ module GrpcKit
36
41
  raise "Duplicated method registered #{path}, class: #{klass}"
37
42
  end
38
43
 
39
- s = handler.is_a?(Class) ? handler.new : handler
40
- @rpc_descs[path] = rpc_desc.build_server(s, interceptors: @interceptors)
44
+ @rpc_descs[path] = rpc_desc.build_server(
45
+ handler.is_a?(Class) ? handler.new : handler,
46
+ interceptors: @interceptors,
47
+ max_receive_message_size: @max_receive_message_size,
48
+ max_send_message_size: @max_send_message_size,
49
+ )
41
50
  end
42
51
  end
43
52
 
@@ -47,7 +56,7 @@ module GrpcKit
47
56
  raise 'Stopping server' if @stopping
48
57
 
49
58
  establish_session(conn) do |s|
50
- s.submit_settings([])
59
+ s.submit_settings(@settings)
51
60
  s.start
52
61
  end
53
62
  end
@@ -18,7 +18,7 @@ module GrpcKit
18
18
 
19
19
  # @param io [GrpcKit::Session::IO]
20
20
  # @param opts [Hash]
21
- def initialize(io, opts = {})
21
+ def initialize(io, **opts)
22
22
  super() # initialize DS9::Session
23
23
 
24
24
  @io = io
@@ -27,6 +27,7 @@ module GrpcKit
27
27
  @draining = false
28
28
  @stop = false
29
29
  @no_write_data = false
30
+ @mutex = Mutex.new
30
31
  end
31
32
 
32
33
  # @param headers [Hash<String,String>]
@@ -47,7 +48,8 @@ module GrpcKit
47
48
  # @param stream_id [Integer]
48
49
  # @return [void]
49
50
  def start(stream_id)
50
- stream = @streams.fetch(stream_id)
51
+ stream = @streams[stream_id]
52
+ return unless stream # stream might have already close
51
53
 
52
54
  loop do
53
55
  if (!want_read? && !want_write?) || stream.close?
@@ -63,26 +65,28 @@ module GrpcKit
63
65
 
64
66
  # @return [void]
65
67
  def run_once
66
- return if @stop
67
-
68
- if @draining && @drain_time < Time.now
69
- raise 'trasport is closing'
70
- end
71
-
72
- if @no_write_data
73
- @io.wait_readable
68
+ @mutex.synchronize do
69
+ return if @stop
74
70
 
75
- if want_read?
76
- do_read
77
- end
78
- else
79
- rs, ws = @io.select
80
- if !rs.empty? && want_read?
81
- do_read
71
+ if @draining && @drain_time < Time.now
72
+ raise 'trasport is closing'
82
73
  end
83
74
 
84
- if !ws.empty? && want_write?
85
- send
75
+ if @no_write_data && !@streams.empty?
76
+ @io.wait_readable
77
+
78
+ if want_read?
79
+ do_read
80
+ end
81
+ else
82
+ rs, ws = @io.select
83
+ if !rs.empty? && want_read?
84
+ do_read
85
+ end
86
+
87
+ if !ws.empty? && want_write?
88
+ send
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -155,8 +159,10 @@ module GrpcKit
155
159
  def on_stream_close(stream_id, error_code)
156
160
  GrpcKit.logger.debug("on_stream_close stream_id=#{stream_id}, error_code=#{error_code}")
157
161
  stream = @streams.delete(stream_id)
158
- return unless stream
159
-
162
+ unless stream
163
+ GrpcKit.logger.warn("on_stream_close stream_id=#{stream_id} not remain on ClientSession")
164
+ return
165
+ end
160
166
  stream.close
161
167
  end
162
168
 
@@ -3,8 +3,9 @@
3
3
  module GrpcKit
4
4
  module Session
5
5
  class ControlQueue
6
- def initialize
6
+ def initialize(waker: proc { })
7
7
  @event_stream = Queue.new
8
+ @waker = waker
8
9
  end
9
10
 
10
11
  # Be nonblocking
@@ -20,14 +21,17 @@ module GrpcKit
20
21
 
21
22
  def submit_response(id, headers)
22
23
  @event_stream.push([:submit_response, id, headers])
24
+ @waker.call(:submit_response)
23
25
  end
24
26
 
25
27
  def submit_headers(id, headers)
26
28
  @event_stream.push([:submit_headers, id, headers])
29
+ @waker.call(:submit_headers)
27
30
  end
28
31
 
29
32
  def resume_data(id)
30
33
  @event_stream.push([:resume_data, id])
34
+ @waker.call(:submit_response)
31
35
  end
32
36
  end
33
37
  end
@@ -53,7 +53,7 @@ module GrpcKit
53
53
  return
54
54
  end
55
55
 
56
- session.submit_goaway(DS9::NO_ERROR, session.last_proc_stream_id)
56
+ session.submit_goaway(session.last_proc_stream_id, DS9::NO_ERROR)
57
57
  next_step
58
58
  when Status::SENT_GOAWAY
59
59
  # session.shutdown
@@ -7,9 +7,12 @@ module GrpcKit
7
7
  class IO
8
8
  def initialize(io)
9
9
  @io = io
10
+ @wake_o, @wake_i = ::IO.pipe
10
11
  end
11
12
 
12
13
  def close
14
+ @wake_i.close
15
+ @wake_o.close
13
16
  @io.close
14
17
  end
15
18
 
@@ -52,8 +55,19 @@ module GrpcKit
52
55
 
53
56
  # Blocking until io object is readable or writable
54
57
  # @return [void]
55
- def select(timeout = 1)
56
- ::IO.select([@io], [@io], [], timeout)
58
+ def select(timeout: 1, write: true)
59
+ rs, ws = ::IO.select([@io, @wake_o], write ? [@io] : [], [], timeout)
60
+ @wake_o.read(@wake_o.stat.size) if rs&.delete(@wake_o) && !@wake_o.closed?
61
+ [rs || [], ws || []]
62
+ end
63
+
64
+ # Wake thread blocked at #select method
65
+ # @param [Symbol] Indicate what event needed to invoke blocking thread. This argument is for debugging purpose.
66
+ def wake!(memo = nil)
67
+ @wake_i.write_nonblock(?\0, exception: false)
68
+ rescue Errno::EPIPE
69
+ rescue IOError
70
+ raise unless @wake_i.closed?
57
71
  end
58
72
 
59
73
  # @return [void]
@@ -3,35 +3,69 @@
3
3
  module GrpcKit
4
4
  module Session
5
5
  class RecvBuffer
6
+ class Closed < Exception; end
7
+
6
8
  def initialize
7
9
  @buffer = +''.b
8
10
  @end = false
9
- @mutex = Mutex.new
11
+ @queue = Queue.new
10
12
  end
11
13
 
12
14
  # @param data [String]
13
15
  # @return [void]
14
16
  def write(data)
15
- @mutex.synchronize { @buffer << data }
17
+ @queue << data
18
+ rescue ClosedQueueError
19
+ raise Closed, "[BUG] write to closed queue"
20
+ end
21
+
22
+ # @return [Boolean]
23
+ def empty?
24
+ @queue.empty?
25
+ end
26
+
27
+ # @return [Boolean]
28
+ def closed?
29
+ @queue.closed?
16
30
  end
17
31
 
32
+ # @return [void]
33
+ def close
34
+ @queue.close
35
+ end
36
+
37
+ # This method is not thread safe (as RecvBuffer is designed to be a multi-producer/single-consumer)
18
38
  # @param size [Integer,nil]
19
39
  # @param last [Boolean]
20
- # @return [String,nil]
21
- def read(size = nil, last: false)
22
- buf = @mutex.synchronize do
23
- return nil if @buffer.empty?
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
34
- end
40
+ # @param blocking [Boolean]
41
+ # @return [String,Symbol,nil]
42
+ def read(size = nil, last: false, blocking:)
43
+ if @buffer.empty?
44
+ return nil if empty? && closed?
45
+ return :wait_readable if empty? && !blocking
46
+
47
+ # Consume existing data as much as possible to continue (important on clients where single-threaded)
48
+ loop do
49
+ begin
50
+ data = @queue.shift(!blocking)
51
+ @buffer << data if data
52
+ rescue ThreadError
53
+ break
54
+ end
55
+
56
+ break if empty?
57
+ end
58
+ end
59
+
60
+ buf = if size.nil? || @buffer.bytesize < size
61
+ rbuf = @buffer
62
+ @buffer = ''.b
63
+ rbuf
64
+ else
65
+ @buffer.freeze
66
+ rbuf = @buffer.byteslice(0, size)
67
+ @buffer = @buffer.byteslice(size, @buffer.bytesize)
68
+ rbuf
35
69
  end
36
70
 
37
71
  end_read if last
@@ -16,14 +16,20 @@ module GrpcKit
16
16
  # @param io [GrpcKit::Session::IO]
17
17
  # @param pool [GrpcKit::RcpDispatcher]
18
18
  def initialize(io, dispatcher)
19
- super() # initialize DS9::Session
19
+ opt = DS9::Option.new.tap do |o|
20
+ # https://github.com/nghttp2/nghttp2/issues/810
21
+ # grpc_kit doesn't need to retain closed stream.
22
+ # This would derease the memory usage.
23
+ o.set_no_closed_streams
24
+ end
25
+ super(option: opt) # initialize DS9::Session
20
26
 
21
27
  @io = io
22
28
  @streams = {}
23
29
  @stop = false
24
30
  @inflights = []
25
31
  @drain_controller = GrpcKit::Session::DrainController.new
26
- @control_queue = GrpcKit::Session::ControlQueue.new
32
+ @control_queue = GrpcKit::Session::ControlQueue.new(waker: @io.method(:wake!))
27
33
  @dispatcher = dispatcher
28
34
  end
29
35
 
@@ -58,7 +64,7 @@ module GrpcKit
58
64
  @drain_controller.next(self)
59
65
  end
60
66
 
61
- rs, ws = @io.select
67
+ rs, ws = @io.select(timeout: 5, write: want_write?)
62
68
 
63
69
  if !rs.empty? && want_read?
64
70
  do_read
@@ -142,6 +148,8 @@ module GrpcKit
142
148
  data = @streams[stream_id].pending_send_data.read(length)
143
149
  if data.nil?
144
150
  submit_trailer(stream_id, stream.trailer_data)
151
+ @io.wake!(:submit_trailer)
152
+
145
153
  # trailer header
146
154
  false
147
155
  else
@@ -174,6 +182,7 @@ module GrpcKit
174
182
  stream.inflight = true
175
183
  @dispatcher.schedule([stream, @control_queue])
176
184
  end
185
+
177
186
  when DS9::Frames::Headers
178
187
  if frame.end_stream?
179
188
  stream = @streams[frame.stream_id]
@@ -13,7 +13,6 @@ module GrpcKit
13
13
 
14
14
  delegate %i[end_write?] => :@pending_send_data
15
15
  delegate %i[end_read?] => :@pending_recv_data
16
- delegate %i[close close_remote close_local close? close_remote? close_local?] => :@status
17
16
 
18
17
  attr_reader :headers, :pending_send_data, :pending_recv_data, :trailer_data, :status
19
18
  attr_accessor :inflight, :stream_id
@@ -59,9 +58,10 @@ module GrpcKit
59
58
  end
60
59
 
61
60
  # @param last [Boolean]
61
+ # @param blocking [Boolean]
62
62
  # @return [void]
63
- def read_recv_data(last: false)
64
- @pending_recv_data.read(last: last)
63
+ def read_recv_data(last: false, blocking:)
64
+ @pending_recv_data.read(last: last, blocking: blocking)
65
65
  end
66
66
 
67
67
  # @param name [String]
@@ -70,6 +70,18 @@ module GrpcKit
70
70
  def add_header(name, value)
71
71
  @headers.add(name, value)
72
72
  end
73
+
74
+ delegate %i[close_local close? close_remote? close_local?] => :@status
75
+
76
+ def close
77
+ status.close
78
+ pending_recv_data.close
79
+ end
80
+
81
+ def close_remote
82
+ status.close_remote
83
+ pending_recv_data.close
84
+ end
73
85
  end
74
86
  end
75
87
  end
@@ -47,6 +47,7 @@ module GrpcKit
47
47
  end
48
48
  end
49
49
 
50
+ # This method is not thread safe, never call from multiple threads at once.
50
51
  # @raise [StopIteration] when recving message finished
51
52
  # @param last [Boolean]
52
53
  # @param blocking [Boolean]
@@ -15,10 +15,10 @@ module GrpcKit
15
15
  def invoke(rpc)
16
16
  rpc.invoke(self, metadata: @transport.recv_headers.metadata)
17
17
  rescue GrpcKit::Errors::BadStatus => e
18
- GrpcKit.logger.debug(e)
18
+ GrpcKit.logger.warn(e)
19
19
  send_status(status: e.code, msg: e.reason, metadata: {}) # TODO: metadata should be set
20
20
  rescue StandardError => e
21
- GrpcKit.logger.debug(e)
21
+ GrpcKit.logger.warn(e)
22
22
  send_status(status: GrpcKit::StatusCodes::UNKNOWN, msg: e.message, metadata: {})
23
23
  end
24
24
 
@@ -50,6 +50,7 @@ module GrpcKit
50
50
  end
51
51
  end
52
52
 
53
+ # This method is not thread safe, never call from multiple threads at once.
53
54
  # @raise [StopIteration] when recving message finished
54
55
  # @param codec [GrpcKit::Codec]
55
56
  # @param last [Boolean]
@@ -89,13 +90,18 @@ module GrpcKit
89
90
  t = build_trailers(status, msg, metadata)
90
91
  @transport.write_data(data, last: true) if data
91
92
 
92
- @transport.end_write
93
- if @started
93
+ if @started # Complete stream
94
94
  @transport.write_trailers(t)
95
- elsif data
95
+ @transport.end_write
96
+
97
+ elsif data # Complete stream with a data
96
98
  @transport.write_trailers(t)
97
- start_response
98
- else
99
+ @transport.end_write
100
+
101
+ start_response # will send queued data and trailer.
102
+
103
+ else # return status (likely non-200) and immediately complete stream.
104
+ @transport.end_write
99
105
  send_headers(trailers: t)
100
106
  end
101
107
  end
@@ -41,20 +41,31 @@ module GrpcKit
41
41
  end
42
42
 
43
43
  # @param last [Boolean]
44
- # @return [nil,String]
44
+ # @return [nil,Array<Boolean,Integer,String>] nil when closed, tuple of Length-Prefixed-Message
45
45
  def read_data(last: false)
46
- unpack(recv_data(last: last))
46
+ data_in_buffer = unpack(nil)
47
+ return data_in_buffer if data_in_buffer
48
+ loop do
49
+ data = recv_data(last: last)
50
+ return unpack(nil) unless data
51
+ message = unpack(data)
52
+ return message if message
53
+ end
47
54
  end
48
55
 
49
56
  # @param last [Boolean]
50
- # @return [nil,String]
57
+ # @return [nil,Array<Boolean,Integer,String>,Symbol] nil when closed, tuple of Length-Prefixed-Message, or :wait_readable
51
58
  def read_data_nonblock(last: false)
59
+ data_in_buffer = unpack(nil)
60
+ return data_in_buffer if data_in_buffer
61
+
52
62
  data = nonblock_recv_data(last: last)
53
63
  if data == :wait_readable
54
- unpack(nil) # nil is needed read buffered data
55
64
  :wait_readable
65
+ elsif data == nil
66
+ return unpack(nil)
56
67
  else
57
- unpack(data)
68
+ unpack(data) || :wait_readable
58
69
  end
59
70
  end
60
71
 
@@ -78,33 +89,26 @@ module GrpcKit
78
89
  end
79
90
 
80
91
  def nonblock_recv_data(last: false)
81
- data = @stream.read_recv_data(last: last)
82
- return data unless data.nil?
83
-
84
- if @stream.close_remote?
85
- return nil
86
- end
87
-
88
- @session.run_once
92
+ data = @stream.read_recv_data(last: last, blocking: false)
93
+ return data if data.is_a?(String)
94
+ return nil unless data
89
95
 
90
96
  :wait_readable
91
97
  end
92
98
 
93
99
  def recv_data(last: false)
94
100
  loop do
95
- data = @stream.read_recv_data(last: last)
96
- return data unless data.nil?
97
-
98
- if @stream.close_remote?
99
- # it do not receive data which we need, it may receive invalid grpc-status
100
- unless @stream.end_read?
101
- return nil
102
- end
103
-
101
+ # FIXME: GrpcKit::Client isn't threaded, this cannot be blocked to trigger ClientSession#run_once appropriately
102
+ # but run_once would block while no outbound requests. Could be problematic on BiDi calls.
103
+ data = @stream.read_recv_data(last: last, blocking: false)
104
+ case data
105
+ when :wait_readable
106
+ @session.run_once
107
+ when String
108
+ return data
109
+ when nil
104
110
  return nil
105
111
  end
106
-
107
- @session.run_once
108
112
  end
109
113
  end
110
114
 
@@ -24,8 +24,8 @@ module GrpcKit
24
24
  end
25
25
 
26
26
  class Unpacker
27
- # Compressed bytes(1 Byte) + length bytes(4 Bytes)
28
- METADATA_SIZE = 5
27
+ # Compressed-Flag (1 byte) + Message-Length (4 Bytes)
28
+ PREFIX_SIZE = 5
29
29
 
30
30
  def initialize
31
31
  @data = +''.b
@@ -44,14 +44,17 @@ module GrpcKit
44
44
 
45
45
  # @return [nil, Array<Boolean, Integer, String>]
46
46
  def read
47
- return nil if @data.empty?
47
+ return nil if @data.bytesize < PREFIX_SIZE
48
+
49
+ prefix = @data.byteslice(0, PREFIX_SIZE)
50
+ compressed, length = prefix.unpack('CN')
51
+
52
+ return nil if (@data.bytesize-PREFIX_SIZE) < length
48
53
 
49
54
  d = @data.freeze
50
- metadata = d.byteslice(0, METADATA_SIZE)
51
- c, size = metadata.unpack('CN')
52
- data = @data.byteslice(METADATA_SIZE, size)
53
- @data = @data.byteslice(METADATA_SIZE + size, @data.bytesize)
54
- [c != 0, size, data]
55
+ data = d.byteslice(PREFIX_SIZE, length)
56
+ @data = d.byteslice(PREFIX_SIZE + length, d.bytesize)
57
+ [compressed == 1, length, data]
55
58
  end
56
59
  end
57
60
  end
@@ -36,9 +36,16 @@ module GrpcKit
36
36
  end
37
37
 
38
38
  # @param last [Boolean]
39
- # @return [nil,String]
39
+ # @return [nil,Array<Boolean,Integer,String>] nil when closed, tuple of Length-Prefixed-Message
40
40
  def read_data(last: false)
41
- unpack(recv_data(last: last))
41
+ data_in_buffer = unpack(nil)
42
+ return data_in_buffer if data_in_buffer
43
+ loop do
44
+ data = recv_data(last: last)
45
+ return nil unless data
46
+ message = unpack(data)
47
+ return message if message
48
+ end
42
49
  end
43
50
 
44
51
  # @param trailer [Hash<String, String>]
@@ -61,17 +68,7 @@ module GrpcKit
61
68
  private
62
69
 
63
70
  def recv_data(last: false)
64
- loop do
65
- data = @stream.read_recv_data(last: last)
66
- return data if data
67
-
68
- if @stream.close_remote?
69
- # Call @stream.read_recv_data after checking @stream.close_remote?
70
- # because of the order of nghttp2 callbacks which calls a callback receiving data before a callback receiving END_STREAM flag
71
- data = @stream.read_recv_data(last: last)
72
- return data
73
- end
74
- end
71
+ @stream.read_recv_data(last: last, blocking: true)
75
72
  end
76
73
 
77
74
  def send_data
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrpcKit
4
- VERSION = '0.3.6'
4
+ VERSION = '0.5.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.3.6
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuta Iwama
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-12 00:00:00.000000000 Z
11
+ date: 2021-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ds9
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.3.3
19
+ version: 1.4.0
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.3.3
26
+ version: 1.4.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: google-protobuf
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 3.6.1
33
+ version: 3.7.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 3.6.1
40
+ version: 3.7.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: googleapis-common-protos-types
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -143,11 +143,11 @@ executables: []
143
143
  extensions: []
144
144
  extra_rdoc_files: []
145
145
  files:
146
+ - ".github/workflows/ci.yml"
146
147
  - ".gitignore"
147
148
  - ".rspec"
148
149
  - ".rubocop.yml"
149
- - ".ruby-version"
150
- - ".travis.yml"
150
+ - CHANGELOG.md
151
151
  - Gemfile
152
152
  - LICENSE.txt
153
153
  - README.md
@@ -237,7 +237,7 @@ homepage: https://github.com/ganmacs/grpc_kit
237
237
  licenses:
238
238
  - MIT
239
239
  metadata: {}
240
- post_install_message:
240
+ post_install_message:
241
241
  rdoc_options: []
242
242
  require_paths:
243
243
  - lib
@@ -252,9 +252,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
252
252
  - !ruby/object:Gem::Version
253
253
  version: '0'
254
254
  requirements: []
255
- rubyforge_project:
256
- rubygems_version: 2.7.6
257
- signing_key:
255
+ rubygems_version: 3.1.4
256
+ signing_key:
258
257
  specification_version: 4
259
258
  summary: A kit for creating gRPC server/client
260
259
  test_files: []
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 2.5
data/.travis.yml DELETED
@@ -1,13 +0,0 @@
1
- language: ruby
2
- cache:
3
- - bundler
4
- dist: trusty
5
- rvm:
6
- - ruby-head
7
- - 2.5.3
8
- - 2.4.5
9
- before_install:
10
- - gem install bundler -v 1.17.0
11
- matrix:
12
- allow_failures:
13
- - rvm: ruby-head