grpc_kit 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +25 -0
- data/CHANGELOG.md +9 -0
- data/README.md +4 -6
- data/grpc_kit.gemspec +2 -2
- data/lib/grpc_kit/calls/client_bidi_streamer.rb +17 -20
- data/lib/grpc_kit/calls/client_server_streamer.rb +1 -0
- data/lib/grpc_kit/calls/server_bidi_streamer.rb +2 -1
- data/lib/grpc_kit/calls/server_client_streamer.rb +2 -1
- data/lib/grpc_kit/calls/server_request_response.rb +1 -1
- data/lib/grpc_kit/calls/server_server_streamer.rb +1 -1
- data/lib/grpc_kit/client.rb +8 -8
- data/lib/grpc_kit/grpc/dsl.rb +9 -9
- data/lib/grpc_kit/server.rb +12 -11
- data/lib/grpc_kit/session/client_session.rb +27 -21
- data/lib/grpc_kit/session/control_queue.rb +5 -1
- data/lib/grpc_kit/session/drain_controller.rb +15 -6
- data/lib/grpc_kit/session/io.rb +16 -2
- data/lib/grpc_kit/session/recv_buffer.rb +51 -17
- data/lib/grpc_kit/session/server_session.rb +17 -7
- data/lib/grpc_kit/session/stream.rb +15 -3
- data/lib/grpc_kit/stream/client_stream.rb +1 -0
- data/lib/grpc_kit/stream/server_stream.rb +13 -7
- data/lib/grpc_kit/transport/client_transport.rb +28 -24
- data/lib/grpc_kit/transport/packable.rb +11 -8
- data/lib/grpc_kit/transport/server_transport.rb +10 -13
- data/lib/grpc_kit/version.rb +1 -1
- metadata +12 -12
- data/.travis.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a609dafaf12831fc04a1ffbf84f38b1551a408aeeeeb0fc22ec59eae33ab607
|
4
|
+
data.tar.gz: b2623749a3c1ec772963e081fafb4ca249fd0afba6e682fe61ccec68dd8e1c6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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'
|
data/CHANGELOG.md
ADDED
@@ -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,10 +1,8 @@
|
|
1
1
|
# GrpcKit
|
2
2
|
|
3
|
-
[](https://github.com/cookpad/grpc_kit/actions)
|
4
4
|
[](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/
|
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/
|
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/
|
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.
|
26
|
-
spec.add_dependency 'google-protobuf', '>= 3.
|
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
|
-
@
|
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
|
-
@
|
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
|
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
|
-
|
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
|
-
@
|
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
|
-
|
58
|
+
@recv_mutex.synchronize do
|
59
|
+
loop { yield(recv) }
|
60
|
+
end
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
@@ -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)
|
data/lib/grpc_kit/client.rb
CHANGED
@@ -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
|
data/lib/grpc_kit/grpc/dsl.rb
CHANGED
@@ -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
|
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
|
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}"
|
data/lib/grpc_kit/server.rb
CHANGED
@@ -10,7 +10,7 @@ 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
|
+
def initialize(interceptors: [], shutdown_timeout: 30, min_pool_size: nil, max_pool_size: nil, settings: [])
|
14
14
|
@interceptors = interceptors
|
15
15
|
@shutdown_timeout = shutdown_timeout
|
16
16
|
@min_pool_size = min_pool_size || GrpcKit::RpcDispatcher::DEFAULT_MIN
|
@@ -19,6 +19,7 @@ module GrpcKit
|
|
19
19
|
@rpc_descs = {}
|
20
20
|
@mutex = Mutex.new
|
21
21
|
@stopping = false
|
22
|
+
@settings = settings
|
22
23
|
|
23
24
|
GrpcKit.logger.debug("Launched grpc_kit(v#{GrpcKit::VERSION})")
|
24
25
|
end
|
@@ -47,7 +48,7 @@ module GrpcKit
|
|
47
48
|
raise 'Stopping server' if @stopping
|
48
49
|
|
49
50
|
establish_session(conn) do |s|
|
50
|
-
s.submit_settings(
|
51
|
+
s.submit_settings(@settings)
|
51
52
|
s.start
|
52
53
|
end
|
53
54
|
end
|
@@ -64,23 +65,23 @@ module GrpcKit
|
|
64
65
|
end
|
65
66
|
|
66
67
|
# This method is expected to be called in trap context
|
68
|
+
# @params timeout [Boolean] timeout error could be raised or not
|
67
69
|
# @return [void]
|
68
|
-
def graceful_shutdown
|
70
|
+
def graceful_shutdown(timeout: true)
|
69
71
|
@stopping = true
|
70
72
|
|
71
73
|
Thread.new do
|
72
74
|
GrpcKit.logger.debug('graceful shutdown')
|
73
75
|
@mutex.synchronize { @sessions.each(&:drain) }
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
shutdown_sessions
|
80
|
-
break
|
81
|
-
elsif @sessions.empty?
|
82
|
-
break
|
77
|
+
begin
|
78
|
+
sec = timeout ? @shutdown_timeout : 0
|
79
|
+
Timeout.timeout(sec) do
|
80
|
+
sleep 1 until @sessions.empty?
|
83
81
|
end
|
82
|
+
rescue Timeout::Error => _
|
83
|
+
GrpcKit.logger.error("Graceful shutdown is timeout (#{@shutdown_timeout}sec). Perform shutdown forceibly")
|
84
|
+
shutdown_sessions
|
84
85
|
end
|
85
86
|
end
|
86
87
|
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
|
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
|
-
|
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
|
76
|
-
|
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
|
85
|
-
|
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
|
-
|
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
|
@@ -8,12 +8,14 @@ module GrpcKit
|
|
8
8
|
module Status
|
9
9
|
NOT_START = 0
|
10
10
|
STARTED = 1
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
SENT_SHUTDOWN = 2
|
12
|
+
SENT_PING = 3
|
13
|
+
RECV_PING_ACK = 4
|
14
|
+
SENT_GOAWAY = 5
|
14
15
|
end
|
15
16
|
|
16
|
-
def initialize
|
17
|
+
def initialize(draining_time = 5)
|
18
|
+
@draining_time = draining_time
|
17
19
|
@status = Status::NOT_START
|
18
20
|
end
|
19
21
|
|
@@ -39,15 +41,22 @@ module GrpcKit
|
|
39
41
|
# next_step
|
40
42
|
when Status::STARTED
|
41
43
|
session.submit_shutdown
|
44
|
+
next_step
|
45
|
+
when Status::SENT_SHUTDOWN
|
42
46
|
session.submit_ping
|
47
|
+
@sent_time = Time.now.to_i
|
43
48
|
next_step
|
44
49
|
when Status::SENT_PING
|
45
50
|
# skip until #recv_ping_ack is called (1RTT)
|
46
51
|
when Status::RECV_PING_ACK
|
47
|
-
|
52
|
+
if @sent_time && (Time.now.to_i - @sent_time) > @draining_time
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
session.submit_goaway(session.last_proc_stream_id, DS9::NO_ERROR)
|
48
57
|
next_step
|
49
58
|
when Status::SENT_GOAWAY
|
50
|
-
session.shutdown
|
59
|
+
# session.shutdown
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
data/lib/grpc_kit/session/io.rb
CHANGED
@@ -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
|
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
|
-
@
|
11
|
+
@queue = Queue.new
|
10
12
|
end
|
11
13
|
|
12
14
|
# @param data [String]
|
13
15
|
# @return [void]
|
14
16
|
def write(data)
|
15
|
-
@
|
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
|
-
# @
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
if
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
|
@@ -54,7 +60,11 @@ module GrpcKit
|
|
54
60
|
return false
|
55
61
|
end
|
56
62
|
|
57
|
-
|
63
|
+
if @drain_controller.start_draining?
|
64
|
+
@drain_controller.next(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
rs, ws = @io.select(timeout: 5, write: want_write?)
|
58
68
|
|
59
69
|
if !rs.empty? && want_read?
|
60
70
|
do_read
|
@@ -138,6 +148,8 @@ module GrpcKit
|
|
138
148
|
data = @streams[stream_id].pending_send_data.read(length)
|
139
149
|
if data.nil?
|
140
150
|
submit_trailer(stream_id, stream.trailer_data)
|
151
|
+
@io.wake!(:submit_trailer)
|
152
|
+
|
141
153
|
# trailer header
|
142
154
|
false
|
143
155
|
else
|
@@ -170,6 +182,7 @@ module GrpcKit
|
|
170
182
|
stream.inflight = true
|
171
183
|
@dispatcher.schedule([stream, @control_queue])
|
172
184
|
end
|
185
|
+
|
173
186
|
when DS9::Frames::Headers
|
174
187
|
if frame.end_stream?
|
175
188
|
stream = @streams[frame.stream_id]
|
@@ -178,10 +191,7 @@ module GrpcKit
|
|
178
191
|
when DS9::Frames::Ping
|
179
192
|
if frame.ping_ack?
|
180
193
|
GrpcKit.logger.debug('ping ack is received')
|
181
|
-
|
182
|
-
# We want to send data in case of ClientStreamer or BidiBstreamer which they are sending data in same stream
|
183
|
-
# So we have to wait to send actual GoAway frame untill timeout or something
|
184
|
-
# @drain_controller.recv_ping_ack
|
194
|
+
@drain_controller.recv_ping_ack
|
185
195
|
end
|
186
196
|
# when DS9::Frames::Goaway
|
187
197
|
# when DS9::Frames::RstStream
|
@@ -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
|
@@ -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.
|
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.
|
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
|
-
@
|
93
|
-
if @started
|
93
|
+
if @started # Complete stream
|
94
94
|
@transport.write_trailers(t)
|
95
|
-
|
95
|
+
@transport.end_write
|
96
|
+
|
97
|
+
elsif data # Complete stream with a data
|
96
98
|
@transport.write_trailers(t)
|
97
|
-
|
98
|
-
|
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(
|
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
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
28
|
-
|
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.
|
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
|
-
|
51
|
-
|
52
|
-
|
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(
|
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
|
-
|
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
|
data/lib/grpc_kit/version.rb
CHANGED
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.
|
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:
|
11
|
+
date: 2020-10-23 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.
|
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.
|
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.
|
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.
|
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,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
|
-
-
|
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
|
@@ -252,9 +253,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
252
253
|
- !ruby/object:Gem::Version
|
253
254
|
version: '0'
|
254
255
|
requirements: []
|
255
|
-
|
256
|
-
|
257
|
-
signing_key:
|
256
|
+
rubygems_version: 3.1.2
|
257
|
+
signing_key:
|
258
258
|
specification_version: 4
|
259
259
|
summary: A kit for creating gRPC server/client
|
260
260
|
test_files: []
|