grpc 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grpc might be problematic. Click here for more details.

Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +10 -0
  5. data/.rubocop_todo.yml +52 -0
  6. data/Gemfile +4 -0
  7. data/README.md +82 -0
  8. data/Rakefile +54 -0
  9. data/bin/apis/google/protobuf/empty.rb +44 -0
  10. data/bin/apis/pubsub_demo.rb +267 -0
  11. data/bin/apis/tech/pubsub/proto/pubsub.rb +174 -0
  12. data/bin/apis/tech/pubsub/proto/pubsub_services.rb +103 -0
  13. data/bin/interop/README.md +8 -0
  14. data/bin/interop/interop_client.rb +334 -0
  15. data/bin/interop/interop_server.rb +192 -0
  16. data/bin/interop/test/cpp/interop/empty.rb +44 -0
  17. data/bin/interop/test/cpp/interop/messages.rb +89 -0
  18. data/bin/interop/test/cpp/interop/test.rb +43 -0
  19. data/bin/interop/test/cpp/interop/test_services.rb +60 -0
  20. data/bin/math.proto +80 -0
  21. data/bin/math.rb +61 -0
  22. data/bin/math_client.rb +147 -0
  23. data/bin/math_server.rb +190 -0
  24. data/bin/math_services.rb +56 -0
  25. data/bin/noproto_client.rb +108 -0
  26. data/bin/noproto_server.rb +112 -0
  27. data/ext/grpc/extconf.rb +76 -0
  28. data/ext/grpc/rb_byte_buffer.c +241 -0
  29. data/ext/grpc/rb_byte_buffer.h +54 -0
  30. data/ext/grpc/rb_call.c +569 -0
  31. data/ext/grpc/rb_call.h +59 -0
  32. data/ext/grpc/rb_channel.c +264 -0
  33. data/ext/grpc/rb_channel.h +49 -0
  34. data/ext/grpc/rb_channel_args.c +154 -0
  35. data/ext/grpc/rb_channel_args.h +52 -0
  36. data/ext/grpc/rb_completion_queue.c +185 -0
  37. data/ext/grpc/rb_completion_queue.h +50 -0
  38. data/ext/grpc/rb_credentials.c +281 -0
  39. data/ext/grpc/rb_credentials.h +50 -0
  40. data/ext/grpc/rb_event.c +361 -0
  41. data/ext/grpc/rb_event.h +53 -0
  42. data/ext/grpc/rb_grpc.c +274 -0
  43. data/ext/grpc/rb_grpc.h +74 -0
  44. data/ext/grpc/rb_metadata.c +215 -0
  45. data/ext/grpc/rb_metadata.h +53 -0
  46. data/ext/grpc/rb_server.c +278 -0
  47. data/ext/grpc/rb_server.h +50 -0
  48. data/ext/grpc/rb_server_credentials.c +210 -0
  49. data/ext/grpc/rb_server_credentials.h +50 -0
  50. data/grpc.gemspec +41 -0
  51. data/lib/grpc.rb +39 -0
  52. data/lib/grpc/core/event.rb +44 -0
  53. data/lib/grpc/core/time_consts.rb +71 -0
  54. data/lib/grpc/errors.rb +61 -0
  55. data/lib/grpc/generic/active_call.rb +536 -0
  56. data/lib/grpc/generic/bidi_call.rb +221 -0
  57. data/lib/grpc/generic/client_stub.rb +413 -0
  58. data/lib/grpc/generic/rpc_desc.rb +150 -0
  59. data/lib/grpc/generic/rpc_server.rb +404 -0
  60. data/lib/grpc/generic/service.rb +235 -0
  61. data/lib/grpc/logconfig.rb +40 -0
  62. data/lib/grpc/version.rb +33 -0
  63. data/spec/alloc_spec.rb +44 -0
  64. data/spec/byte_buffer_spec.rb +67 -0
  65. data/spec/call_spec.rb +163 -0
  66. data/spec/channel_spec.rb +181 -0
  67. data/spec/client_server_spec.rb +372 -0
  68. data/spec/completion_queue_spec.rb +74 -0
  69. data/spec/credentials_spec.rb +71 -0
  70. data/spec/event_spec.rb +53 -0
  71. data/spec/generic/active_call_spec.rb +373 -0
  72. data/spec/generic/client_stub_spec.rb +519 -0
  73. data/spec/generic/rpc_desc_spec.rb +357 -0
  74. data/spec/generic/rpc_server_pool_spec.rb +139 -0
  75. data/spec/generic/rpc_server_spec.rb +404 -0
  76. data/spec/generic/service_spec.rb +342 -0
  77. data/spec/metadata_spec.rb +64 -0
  78. data/spec/server_credentials_spec.rb +69 -0
  79. data/spec/server_spec.rb +212 -0
  80. data/spec/spec_helper.rb +51 -0
  81. data/spec/testdata/README +1 -0
  82. data/spec/testdata/ca.pem +15 -0
  83. data/spec/testdata/server1.key +16 -0
  84. data/spec/testdata/server1.pem +16 -0
  85. data/spec/time_consts_spec.rb +89 -0
  86. metadata +353 -0
@@ -0,0 +1,221 @@
1
+ # Copyright 2015, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'forwardable'
31
+ require 'grpc/grpc'
32
+
33
+ def assert_event_type(ev, want)
34
+ fail OutOfTime if ev.nil?
35
+ got = ev.type
36
+ fail("Unexpected rpc event: got #{got}, want #{want}") unless got == want
37
+ end
38
+
39
+ # GRPC contains the General RPC module.
40
+ module GRPC
41
+ # The BiDiCall class orchestrates exection of a BiDi stream on a client or
42
+ # server.
43
+ class BidiCall
44
+ include Core::CompletionType
45
+ include Core::StatusCodes
46
+ include Core::TimeConsts
47
+
48
+ # Creates a BidiCall.
49
+ #
50
+ # BidiCall should only be created after a call is accepted. That means
51
+ # different things on a client and a server. On the client, the call is
52
+ # accepted after call.invoke. On the server, this is after call.accept.
53
+ #
54
+ # #initialize cannot determine if the call is accepted or not; so if a
55
+ # call that's not accepted is used here, the error won't be visible until
56
+ # the BidiCall#run is called.
57
+ #
58
+ # deadline is the absolute deadline for the call.
59
+ #
60
+ # @param call [Call] the call used by the ActiveCall
61
+ # @param q [CompletionQueue] the completion queue used to accept
62
+ # the call
63
+ # @param marshal [Function] f(obj)->string that marshal requests
64
+ # @param unmarshal [Function] f(string)->obj that unmarshals responses
65
+ # @param deadline [Fixnum] the deadline for the call to complete
66
+ # @param finished_tag [Object] the object used as the call's finish tag,
67
+ def initialize(call, q, marshal, unmarshal, deadline, finished_tag)
68
+ fail(ArgumentError, 'not a call') unless call.is_a? Core::Call
69
+ unless q.is_a? Core::CompletionQueue
70
+ fail(ArgumentError, 'not a CompletionQueue')
71
+ end
72
+ @call = call
73
+ @cq = q
74
+ @deadline = deadline
75
+ @finished_tag = finished_tag
76
+ @marshal = marshal
77
+ @readq = Queue.new
78
+ @unmarshal = unmarshal
79
+ end
80
+
81
+ # Begins orchestration of the Bidi stream for a client sending requests.
82
+ #
83
+ # The method either returns an Enumerator of the responses, or accepts a
84
+ # block that can be invoked with each response.
85
+ #
86
+ # @param requests the Enumerable of requests to send
87
+ # @return an Enumerator of requests to yield
88
+ def run_on_client(requests, &blk)
89
+ enq_th = start_write_loop(requests)
90
+ loop_th = start_read_loop
91
+ replies = each_queued_msg
92
+ return replies if blk.nil?
93
+ replies.each { |r| blk.call(r) }
94
+ enq_th.join
95
+ loop_th.join
96
+ end
97
+
98
+ # Begins orchestration of the Bidi stream for a server generating replies.
99
+ #
100
+ # N.B. gen_each_reply is a func(Enumerable<Requests>)
101
+ #
102
+ # It takes an enumerable of requests as an arg, in case there is a
103
+ # relationship between the stream of requests and the stream of replies.
104
+ #
105
+ # This does not mean that must necessarily be one. E.g, the replies
106
+ # produced by gen_each_reply could ignore the received_msgs
107
+ #
108
+ # @param gen_each_reply [Proc] generates the BiDi stream replies.
109
+ def run_on_server(gen_each_reply)
110
+ replys = gen_each_reply.call(each_queued_msg)
111
+ enq_th = start_write_loop(replys, is_client: false)
112
+ loop_th = start_read_loop
113
+ loop_th.join
114
+ enq_th.join
115
+ end
116
+
117
+ private
118
+
119
+ END_OF_READS = :end_of_reads
120
+ END_OF_WRITES = :end_of_writes
121
+
122
+ # each_queued_msg yields each message on this instances readq
123
+ #
124
+ # - messages are added to the readq by #read_loop
125
+ # - iteration ends when the instance itself is added
126
+ def each_queued_msg
127
+ return enum_for(:each_queued_msg) unless block_given?
128
+ count = 0
129
+ loop do
130
+ logger.debug("each_queued_msg: msg##{count}")
131
+ count += 1
132
+ req = @readq.pop
133
+ throw req if req.is_a? StandardError
134
+ break if req.equal?(END_OF_READS)
135
+ yield req
136
+ end
137
+ end
138
+
139
+ # during bidi-streaming, read the requests to send from a separate thread
140
+ # read so that read_loop does not block waiting for requests to read.
141
+ def start_write_loop(requests, is_client: true)
142
+ Thread.new do # TODO: run on a thread pool
143
+ write_tag = Object.new
144
+ begin
145
+ count = 0
146
+ requests.each do |req|
147
+ count += 1
148
+ payload = @marshal.call(req)
149
+ @call.start_write(Core::ByteBuffer.new(payload), write_tag)
150
+ ev = @cq.pluck(write_tag, INFINITE_FUTURE)
151
+ begin
152
+ assert_event_type(ev, WRITE_ACCEPTED)
153
+ ensure
154
+ ev.close
155
+ end
156
+ end
157
+ if is_client
158
+ @call.writes_done(write_tag)
159
+ ev = @cq.pluck(write_tag, INFINITE_FUTURE)
160
+ begin
161
+ assert_event_type(ev, FINISH_ACCEPTED)
162
+ ensure
163
+ ev.close
164
+ end
165
+ logger.debug("bidi-client: sent #{count} reqs, waiting to finish")
166
+ ev = @cq.pluck(@finished_tag, INFINITE_FUTURE)
167
+ begin
168
+ assert_event_type(ev, FINISHED)
169
+ ensure
170
+ ev.close
171
+ end
172
+ logger.debug('bidi-client: finished received')
173
+ end
174
+ rescue StandardError => e
175
+ logger.warn('bidi: write_loop failed')
176
+ logger.warn(e)
177
+ end
178
+ end
179
+ end
180
+
181
+ # starts the read loop
182
+ def start_read_loop
183
+ Thread.new do
184
+ begin
185
+ read_tag = Object.new
186
+ count = 0
187
+
188
+ # queue the initial read before beginning the loop
189
+ loop do
190
+ logger.debug("waiting for read #{count}")
191
+ count += 1
192
+ @call.start_read(read_tag)
193
+ ev = @cq.pluck(read_tag, INFINITE_FUTURE)
194
+ begin
195
+ assert_event_type(ev, READ)
196
+
197
+ # handle the next event.
198
+ if ev.result.nil?
199
+ @readq.push(END_OF_READS)
200
+ logger.debug('done reading!')
201
+ break
202
+ end
203
+
204
+ # push the latest read onto the queue and continue reading
205
+ logger.debug("received req: #{ev.result}")
206
+ res = @unmarshal.call(ev.result.to_s)
207
+ @readq.push(res)
208
+ ensure
209
+ ev.close
210
+ end
211
+ end
212
+
213
+ rescue StandardError => e
214
+ logger.warn('bidi: read_loop failed')
215
+ logger.warn(e)
216
+ @readq.push(e) # let each_queued_msg terminate with this error
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,413 @@
1
+ # Copyright 2015, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'grpc/generic/active_call'
31
+ require 'xray/thread_dump_signal_handler'
32
+
33
+ # GRPC contains the General RPC module.
34
+ module GRPC
35
+ # ClientStub represents an endpoint used to send requests to GRPC servers.
36
+ class ClientStub
37
+ include Core::StatusCodes
38
+
39
+ # Default deadline is 5 seconds.
40
+ DEFAULT_DEADLINE = 5
41
+
42
+ # Creates a new ClientStub.
43
+ #
44
+ # Minimally, a stub is created with the just the host of the gRPC service
45
+ # it wishes to access, e.g.,
46
+ #
47
+ # my_stub = ClientStub.new(example.host.com:50505)
48
+ #
49
+ # Any arbitrary keyword arguments are treated as channel arguments used to
50
+ # configure the RPC connection to the host.
51
+ #
52
+ # There are some specific keyword args that are not used to configure the
53
+ # channel:
54
+ #
55
+ # - :channel_override
56
+ # when present, this must be a pre-created GRPC::Channel. If it's
57
+ # present the host and arbitrary keyword arg areignored, and the RPC
58
+ # connection uses this channel.
59
+ #
60
+ # - :deadline
61
+ # when present, this is the default deadline used for calls
62
+ #
63
+ # - :update_metadata
64
+ # when present, this a func that takes a hash and returns a hash
65
+ # it can be used to update metadata, i.e, remove, change or update
66
+ # amend metadata values.
67
+ #
68
+ # @param host [String] the host the stub connects to
69
+ # @param q [Core::CompletionQueue] used to wait for events
70
+ # @param channel_override [Core::Channel] a pre-created channel
71
+ # @param deadline [Number] the default deadline to use in requests
72
+ # @param creds [Core::Credentials] the channel
73
+ # @param update_metadata a func that updates metadata as described above
74
+ # @param kw [KeywordArgs]the channel arguments
75
+ def initialize(host, q,
76
+ channel_override:nil,
77
+ deadline: DEFAULT_DEADLINE,
78
+ creds: nil,
79
+ update_metadata: nil,
80
+ **kw)
81
+ unless q.is_a? Core::CompletionQueue
82
+ fail(ArgumentError, 'not a CompletionQueue')
83
+ end
84
+ @queue = q
85
+
86
+ # set the channel instance
87
+ if !channel_override.nil?
88
+ ch = channel_override
89
+ fail(ArgumentError, 'not a Channel') unless ch.is_a? Core::Channel
90
+ else
91
+ if creds.nil?
92
+ ch = Core::Channel.new(host, kw)
93
+ elsif !creds.is_a?(Core::Credentials)
94
+ fail(ArgumentError, 'not a Credentials')
95
+ else
96
+ ch = Core::Channel.new(host, kw, creds)
97
+ end
98
+ end
99
+ @ch = ch
100
+
101
+ @update_metadata = nil
102
+ unless update_metadata.nil?
103
+ unless update_metadata.is_a? Proc
104
+ fail(ArgumentError, 'update_metadata is not a Proc')
105
+ end
106
+ @update_metadata = update_metadata
107
+ end
108
+
109
+ @host = host
110
+ @deadline = deadline
111
+ end
112
+
113
+ # request_response sends a request to a GRPC server, and returns the
114
+ # response.
115
+ #
116
+ # == Flow Control ==
117
+ # This is a blocking call.
118
+ #
119
+ # * it does not return until a response is received.
120
+ #
121
+ # * the requests is sent only when GRPC core's flow control allows it to
122
+ # be sent.
123
+ #
124
+ # == Errors ==
125
+ # An RuntimeError is raised if
126
+ #
127
+ # * the server responds with a non-OK status
128
+ #
129
+ # * the deadline is exceeded
130
+ #
131
+ # == Return Value ==
132
+ #
133
+ # If return_op is false, the call returns the response
134
+ #
135
+ # If return_op is true, the call returns an Operation, calling execute
136
+ # on the Operation returns the response.
137
+ #
138
+ # == Keyword Args ==
139
+ #
140
+ # Unspecified keyword arguments are treated as metadata to be sent to the
141
+ # server.
142
+ #
143
+ # @param method [String] the RPC method to call on the GRPC server
144
+ # @param req [Object] the request sent to the server
145
+ # @param marshal [Function] f(obj)->string that marshals requests
146
+ # @param unmarshal [Function] f(string)->obj that unmarshals responses
147
+ # @param deadline [Numeric] (optional) the max completion time in seconds
148
+ # @param return_op [true|false] return an Operation if true
149
+ # @return [Object] the response received from the server
150
+ def request_response(method, req, marshal, unmarshal, deadline = nil,
151
+ return_op: false, **kw)
152
+ c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
153
+ md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
154
+ return c.request_response(req, **md) unless return_op
155
+
156
+ # return the operation view of the active_call; define #execute as a
157
+ # new method for this instance that invokes #request_response.
158
+ op = c.operation
159
+ op.define_singleton_method(:execute) do
160
+ c.request_response(req, **md)
161
+ end
162
+ op
163
+ end
164
+
165
+ # client_streamer sends a stream of requests to a GRPC server, and
166
+ # returns a single response.
167
+ #
168
+ # requests provides an 'iterable' of Requests. I.e. it follows Ruby's
169
+ # #each enumeration protocol. In the simplest case, requests will be an
170
+ # array of marshallable objects; in typical case it will be an Enumerable
171
+ # that allows dynamic construction of the marshallable objects.
172
+ #
173
+ # == Flow Control ==
174
+ # This is a blocking call.
175
+ #
176
+ # * it does not return until a response is received.
177
+ #
178
+ # * each requests is sent only when GRPC core's flow control allows it to
179
+ # be sent.
180
+ #
181
+ # == Errors ==
182
+ # An RuntimeError is raised if
183
+ #
184
+ # * the server responds with a non-OK status
185
+ #
186
+ # * the deadline is exceeded
187
+ #
188
+ # == Return Value ==
189
+ #
190
+ # If return_op is false, the call consumes the requests and returns
191
+ # the response.
192
+ #
193
+ # If return_op is true, the call returns the response.
194
+ #
195
+ # == Keyword Args ==
196
+ #
197
+ # Unspecified keyword arguments are treated as metadata to be sent to the
198
+ # server.
199
+ #
200
+ # @param method [String] the RPC method to call on the GRPC server
201
+ # @param requests [Object] an Enumerable of requests to send
202
+ # @param marshal [Function] f(obj)->string that marshals requests
203
+ # @param unmarshal [Function] f(string)->obj that unmarshals responses
204
+ # @param deadline [Numeric] the max completion time in seconds
205
+ # @param return_op [true|false] return an Operation if true
206
+ # @return [Object|Operation] the response received from the server
207
+ def client_streamer(method, requests, marshal, unmarshal, deadline = nil,
208
+ return_op: false, **kw)
209
+ c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
210
+ md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
211
+ return c.client_streamer(requests, **md) unless return_op
212
+
213
+ # return the operation view of the active_call; define #execute as a
214
+ # new method for this instance that invokes #client_streamer.
215
+ op = c.operation
216
+ op.define_singleton_method(:execute) do
217
+ c.client_streamer(requests, **md)
218
+ end
219
+ op
220
+ end
221
+
222
+ # server_streamer sends one request to the GRPC server, which yields a
223
+ # stream of responses.
224
+ #
225
+ # responses provides an enumerator over the streamed responses, i.e. it
226
+ # follows Ruby's #each iteration protocol. The enumerator blocks while
227
+ # waiting for each response, stops when the server signals that no
228
+ # further responses will be supplied. If the implicit block is provided,
229
+ # it is executed with each response as the argument and no result is
230
+ # returned.
231
+ #
232
+ # == Flow Control ==
233
+ # This is a blocking call.
234
+ #
235
+ # * the request is sent only when GRPC core's flow control allows it to
236
+ # be sent.
237
+ #
238
+ # * the request will not complete until the server sends the final
239
+ # response followed by a status message.
240
+ #
241
+ # == Errors ==
242
+ # An RuntimeError is raised if
243
+ #
244
+ # * the server responds with a non-OK status when any response is
245
+ # * retrieved
246
+ #
247
+ # * the deadline is exceeded
248
+ #
249
+ # == Return Value ==
250
+ #
251
+ # if the return_op is false, the return value is an Enumerator of the
252
+ # results, unless a block is provided, in which case the block is
253
+ # executed with each response.
254
+ #
255
+ # if return_op is true, the function returns an Operation whose #execute
256
+ # method runs server streamer call. Again, Operation#execute either
257
+ # calls the given block with each response or returns an Enumerator of the
258
+ # responses.
259
+ #
260
+ # == Keyword Args ==
261
+ #
262
+ # Unspecified keyword arguments are treated as metadata to be sent to the
263
+ # server.
264
+ #
265
+ # @param method [String] the RPC method to call on the GRPC server
266
+ # @param req [Object] the request sent to the server
267
+ # @param marshal [Function] f(obj)->string that marshals requests
268
+ # @param unmarshal [Function] f(string)->obj that unmarshals responses
269
+ # @param deadline [Numeric] the max completion time in seconds
270
+ # @param return_op [true|false]return an Operation if true
271
+ # @param blk [Block] when provided, is executed for each response
272
+ # @return [Enumerator|Operation|nil] as discussed above
273
+ def server_streamer(method, req, marshal, unmarshal, deadline = nil,
274
+ return_op: false, **kw, &blk)
275
+ c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
276
+ md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
277
+ return c.server_streamer(req, **md, &blk) unless return_op
278
+
279
+ # return the operation view of the active_call; define #execute
280
+ # as a new method for this instance that invokes #server_streamer
281
+ op = c.operation
282
+ op.define_singleton_method(:execute) do
283
+ c.server_streamer(req, **md, &blk)
284
+ end
285
+ op
286
+ end
287
+
288
+ # bidi_streamer sends a stream of requests to the GRPC server, and yields
289
+ # a stream of responses.
290
+ #
291
+ # This method takes an Enumerable of requests, and returns and enumerable
292
+ # of responses.
293
+ #
294
+ # == requests ==
295
+ #
296
+ # requests provides an 'iterable' of Requests. I.e. it follows Ruby's
297
+ # #each enumeration protocol. In the simplest case, requests will be an
298
+ # array of marshallable objects; in typical case it will be an
299
+ # Enumerable that allows dynamic construction of the marshallable
300
+ # objects.
301
+ #
302
+ # == responses ==
303
+ #
304
+ # This is an enumerator of responses. I.e, its #next method blocks
305
+ # waiting for the next response. Also, if at any point the block needs
306
+ # to consume all the remaining responses, this can be done using #each or
307
+ # #collect. Calling #each or #collect should only be done if
308
+ # the_call#writes_done has been called, otherwise the block will loop
309
+ # forever.
310
+ #
311
+ # == Flow Control ==
312
+ # This is a blocking call.
313
+ #
314
+ # * the call completes when the next call to provided block returns
315
+ # * [False]
316
+ #
317
+ # * the execution block parameters are two objects for sending and
318
+ # receiving responses, each of which blocks waiting for flow control.
319
+ # E.g, calles to bidi_call#remote_send will wait until flow control
320
+ # allows another write before returning; and obviously calls to
321
+ # responses#next block until the next response is available.
322
+ #
323
+ # == Termination ==
324
+ #
325
+ # As well as sending and receiving messages, the block passed to the
326
+ # function is also responsible for:
327
+ #
328
+ # * calling bidi_call#writes_done to indicate no further reqs will be
329
+ # sent.
330
+ #
331
+ # * returning false if once the bidi stream is functionally completed.
332
+ #
333
+ # Note that response#next will indicate that there are no further
334
+ # responses by throwing StopIteration, but can only happen either
335
+ # if bidi_call#writes_done is called.
336
+ #
337
+ # To terminate the RPC correctly the block:
338
+ #
339
+ # * must call bidi#writes_done and then
340
+ #
341
+ # * either return false as soon as there is no need for other responses
342
+ #
343
+ # * loop on responses#next until no further responses are available
344
+ #
345
+ # == Errors ==
346
+ # An RuntimeError is raised if
347
+ #
348
+ # * the server responds with a non-OK status when any response is
349
+ # * retrieved
350
+ #
351
+ # * the deadline is exceeded
352
+ #
353
+ #
354
+ # == Keyword Args ==
355
+ #
356
+ # Unspecified keyword arguments are treated as metadata to be sent to the
357
+ # server.
358
+ #
359
+ # == Return Value ==
360
+ #
361
+ # if the return_op is false, the return value is an Enumerator of the
362
+ # results, unless a block is provided, in which case the block is
363
+ # executed with each response.
364
+ #
365
+ # if return_op is true, the function returns an Operation whose #execute
366
+ # method runs the Bidi call. Again, Operation#execute either calls a
367
+ # given block with each response or returns an Enumerator of the
368
+ # responses.
369
+ #
370
+ # @param method [String] the RPC method to call on the GRPC server
371
+ # @param requests [Object] an Enumerable of requests to send
372
+ # @param marshal [Function] f(obj)->string that marshals requests
373
+ # @param unmarshal [Function] f(string)->obj that unmarshals responses
374
+ # @param deadline [Numeric] (optional) the max completion time in seconds
375
+ # @param blk [Block] when provided, is executed for each response
376
+ # @param return_op [true|false] return an Operation if true
377
+ # @return [Enumerator|nil|Operation] as discussed above
378
+ def bidi_streamer(method, requests, marshal, unmarshal, deadline = nil,
379
+ return_op: false, **kw, &blk)
380
+ c = new_active_call(method, marshal, unmarshal, deadline || @deadline)
381
+ md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
382
+ return c.bidi_streamer(requests, **md, &blk) unless return_op
383
+
384
+ # return the operation view of the active_call; define #execute
385
+ # as a new method for this instance that invokes #bidi_streamer
386
+ op = c.operation
387
+ op.define_singleton_method(:execute) do
388
+ c.bidi_streamer(requests, **md, &blk)
389
+ end
390
+ op
391
+ end
392
+
393
+ private
394
+
395
+ # Creates a new active stub
396
+ #
397
+ # @param ch [GRPC::Channel] the channel used to create the stub.
398
+ # @param marshal [Function] f(obj)->string that marshals requests
399
+ # @param unmarshal [Function] f(string)->obj that unmarshals responses
400
+ # @param deadline [TimeConst]
401
+ def new_active_call(ch, marshal, unmarshal, deadline = nil)
402
+ absolute_deadline = Core::TimeConsts.from_relative_time(deadline)
403
+ # It should be OK to to pass the hostname:port to create_call, but at
404
+ # the moment this fails a security check. This will be corrected.
405
+ #
406
+ # TODO: # remove this after create_call is updated
407
+ host = @host.split(':')[0]
408
+ call = @ch.create_call(ch, host, absolute_deadline)
409
+ ActiveCall.new(call, @queue, marshal, unmarshal, absolute_deadline,
410
+ started: false)
411
+ end
412
+ end
413
+ end