grpc_kit 0.3.9 → 0.4.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: 6ac10170b0067b5b136488dc0fe600c398ba766036b5586b95ba73d078226678
4
- data.tar.gz: 6f62b35ab4cdeeaf4ad8a0b2d1f7865281fdea95525e32d91a9f72ecac982b61
3
+ metadata.gz: 4a609dafaf12831fc04a1ffbf84f38b1551a408aeeeeb0fc22ec59eae33ab607
4
+ data.tar.gz: b2623749a3c1ec772963e081fafb4ca249fd0afba6e682fe61ccec68dd8e1c6a
5
5
  SHA512:
6
- metadata.gz: ef362c8bc6bbe89a3793826dbccd7501b63112b7e8da64c81d24dd2d6657828c0fda0dc16cc8b924016f99319c38980a4ac3fa5711015a965d037eadff542d3a
7
- data.tar.gz: 614e6ac35969ff541457b3c63af01b56e78ed4dffe627a50113ecf9a75b93c97b091a76c4c23e956fa957945be95283a5721f6546e3a77696e1671bf72d881b0
6
+ metadata.gz: bbf6bad8922e0058979e7e1962aed08ff7625f24691ae55cd62fc5a9ae6c53a6a68b1938050d17320b972b861e37edf1435e48497d63186863ed8e452c736eaa
7
+ data.tar.gz: aabd06f81259f04e8ea051466d88b5160cc062422f070a1bb6cb4945db43747ae488cbbe6d2f4af0bd97dfcf159c6705d52ceaaa7a6b916f7fc31b2f1378abf0
@@ -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'
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## v0.4.0 (2020-10-23)
4
+
5
+ - bug: Fix Ruby 2.7 keyword argument separation warnings ([#27](https://github.com/cookpad/grpc_kit/pull/27))
6
+ - bug: HTTP/2 Trailer (grpc-status) might not be sent due to race condition ([#30](https://github.com/cookpad/grpc_kit/pull/30))
7
+ - improve: Reduce number of select(2) calls by adding pipe(2) to wake blocking threads ([#28](https://github.com/cookpad/grpc_kit/pull/28))
8
+ - improve: Improved performance when receiving streaming messages by blocking queue. ([#31](https://github.com/cookpad/grpc_kit/pull/31))
9
+
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GrpcKit
2
2
 
3
- [![Build Status](https://travis-ci.org/cookpad/grpc_kit.svg?branch=master)](https://travis-ci.org/cookpad/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
6
  A kit for creating [gRPC](https://grpc.io/) server/client in Ruby.
@@ -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 = {}
@@ -30,32 +30,32 @@ module GrpcKit
30
30
  # @param rpc [GrpcKit::Rpcs::Client::RequestResponse]
31
31
  # @param request [Object]
32
32
  # @param opts [Hash]
33
- def request_response(rpc, request, opts = {})
33
+ def request_response(rpc, request, **opts)
34
34
  GrpcKit.logger.debug('Calling request_respose')
35
- do_request(rpc, request, opts)
35
+ do_request(rpc, request, **opts)
36
36
  end
37
37
 
38
38
  # @param rpc [GrpcKit::Rpcs::Client::ClientStreamer]
39
39
  # @param opts [Hash]
40
- def client_streamer(rpc, opts = {})
40
+ def client_streamer(rpc, **opts)
41
41
  GrpcKit.logger.debug('Calling client_streamer')
42
- do_request(rpc, nil, opts)
42
+ do_request(rpc, nil, **opts)
43
43
  end
44
44
 
45
45
  # @param rpc [GrpcKit::Rpcs::Client::ServerStreamer]
46
46
  # @param request [Object]
47
47
  # @param opts [Hash]
48
- def server_streamer(rpc, request, opts = {})
48
+ def server_streamer(rpc, request, **opts)
49
49
  GrpcKit.logger.debug('Calling server_streamer')
50
- do_request(rpc, request, opts)
50
+ do_request(rpc, request, **opts)
51
51
  end
52
52
 
53
53
  # @param rpc [GrpcKit::Rpcs::Client::ServerStreamer]
54
54
  # @param _requests [Object] it's for compatibility, no use
55
55
  # @param opts [Hash]
56
- def bidi_streamer(rpc, _requests, opts = {})
56
+ def bidi_streamer(rpc, _requests, **opts)
57
57
  GrpcKit.logger.debug('Calling bidi_streamer')
58
- do_request(rpc, nil, opts)
58
+ do_request(rpc, nil, **opts)
59
59
  end
60
60
 
61
61
  private
@@ -68,7 +68,7 @@ module GrpcKit
68
68
  end
69
69
 
70
70
  Class.new(GrpcKit::Client) do
71
- def initialize(*)
71
+ def initialize(*, **)
72
72
  @rpcs = {}
73
73
  super
74
74
  end
@@ -82,20 +82,20 @@ module GrpcKit
82
82
 
83
83
  rpc_descs_.each do |method_name, rpc_desc|
84
84
  if rpc_desc.request_response?
85
- define_method(method_name) do |request, opts = {}|
86
- request_response(@rpcs.fetch(method_name), request, opts)
85
+ define_method(method_name) do |request, **opts|
86
+ request_response(@rpcs.fetch(method_name), request, **opts)
87
87
  end
88
88
  elsif rpc_desc.client_streamer?
89
- define_method(method_name) do |opts = {}|
90
- client_streamer(@rpcs.fetch(method_name), opts)
89
+ define_method(method_name) do |**opts|
90
+ client_streamer(@rpcs.fetch(method_name), **opts)
91
91
  end
92
92
  elsif rpc_desc.server_streamer?
93
- define_method(method_name) do |request, opts = {}|
94
- server_streamer(@rpcs.fetch(method_name), request, opts)
93
+ define_method(method_name) do |request, **opts|
94
+ server_streamer(@rpcs.fetch(method_name), request, **opts)
95
95
  end
96
96
  elsif rpc_desc.bidi_streamer?
97
- define_method(method_name) do |requests, opts = {}, &blk|
98
- bidi_streamer(@rpcs.fetch(method_name), requests, opts, &blk)
97
+ define_method(method_name) do |requests, **opts, &blk|
98
+ bidi_streamer(@rpcs.fetch(method_name), requests, **opts, &blk)
99
99
  end
100
100
  else
101
101
  raise "unknown #{rpc_desc}"
@@ -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
@@ -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
@@ -29,7 +29,7 @@ module GrpcKit
29
29
  @stop = false
30
30
  @inflights = []
31
31
  @drain_controller = GrpcKit::Session::DrainController.new
32
- @control_queue = GrpcKit::Session::ControlQueue.new
32
+ @control_queue = GrpcKit::Session::ControlQueue.new(waker: @io.method(:wake!))
33
33
  @dispatcher = dispatcher
34
34
  end
35
35
 
@@ -64,7 +64,7 @@ module GrpcKit
64
64
  @drain_controller.next(self)
65
65
  end
66
66
 
67
- rs, ws = @io.select
67
+ rs, ws = @io.select(timeout: 5, write: want_write?)
68
68
 
69
69
  if !rs.empty? && want_read?
70
70
  do_read
@@ -148,6 +148,8 @@ module GrpcKit
148
148
  data = @streams[stream_id].pending_send_data.read(length)
149
149
  if data.nil?
150
150
  submit_trailer(stream_id, stream.trailer_data)
151
+ @io.wake!(:submit_trailer)
152
+
151
153
  # trailer header
152
154
  false
153
155
  else
@@ -180,6 +182,7 @@ module GrpcKit
180
182
  stream.inflight = true
181
183
  @dispatcher.schedule([stream, @control_queue])
182
184
  end
185
+
183
186
  when DS9::Frames::Headers
184
187
  if frame.end_stream?
185
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]
@@ -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.9'
4
+ VERSION = '0.4.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.9
4
+ version: 0.4.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: 2020-03-02 00:00:00.000000000 Z
11
+ date: 2020-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ds9
@@ -143,11 +143,12 @@ 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
150
  - ".ruby-version"
150
- - ".travis.yml"
151
+ - CHANGELOG.md
151
152
  - Gemfile
152
153
  - LICENSE.txt
153
154
  - README.md
@@ -237,7 +238,7 @@ homepage: https://github.com/ganmacs/grpc_kit
237
238
  licenses:
238
239
  - MIT
239
240
  metadata: {}
240
- post_install_message:
241
+ post_install_message:
241
242
  rdoc_options: []
242
243
  require_paths:
243
244
  - lib
@@ -253,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
253
254
  version: '0'
254
255
  requirements: []
255
256
  rubygems_version: 3.1.2
256
- signing_key:
257
+ signing_key:
257
258
  specification_version: 4
258
259
  summary: A kit for creating gRPC server/client
259
260
  test_files: []
@@ -1,14 +0,0 @@
1
- language: ruby
2
- cache:
3
- - bundler
4
- dist: trusty
5
- rvm:
6
- - ruby-head
7
- - 2.6.1
8
- - 2.5.3
9
- - 2.4.5
10
- before_install:
11
- - gem install bundler -v 1.17.0
12
- matrix:
13
- allow_failures:
14
- - rvm: ruby-head