grpc 1.0.1-universal-darwin → 1.1.2-universal-darwin

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/etc/roots.pem +39 -111
  3. data/src/ruby/ext/grpc/extconf.rb +0 -1
  4. data/src/ruby/ext/grpc/rb_byte_buffer.c +8 -7
  5. data/src/ruby/ext/grpc/rb_call.c +15 -5
  6. data/src/ruby/ext/grpc/rb_channel.c +1 -1
  7. data/src/ruby/ext/grpc/rb_compression_options.c +466 -0
  8. data/src/ruby/ext/grpc/rb_compression_options.h +44 -0
  9. data/src/ruby/ext/grpc/rb_grpc.c +3 -1
  10. data/src/ruby/ext/grpc/rb_grpc_imports.generated.c +198 -190
  11. data/src/ruby/ext/grpc/rb_grpc_imports.generated.h +306 -294
  12. data/src/ruby/ext/grpc/rb_server.c +18 -12
  13. data/src/ruby/lib/grpc/2.0/grpc_c.bundle +0 -0
  14. data/src/ruby/lib/grpc/2.1/grpc_c.bundle +0 -0
  15. data/src/ruby/lib/grpc/2.2/grpc_c.bundle +0 -0
  16. data/src/ruby/lib/grpc/2.3/grpc_c.bundle +0 -0
  17. data/src/ruby/lib/grpc/2.4/grpc_c.bundle +0 -0
  18. data/src/ruby/lib/grpc/errors.rb +154 -2
  19. data/src/ruby/lib/grpc/generic/active_call.rb +144 -63
  20. data/src/ruby/lib/grpc/generic/bidi_call.rb +18 -2
  21. data/src/ruby/lib/grpc/generic/client_stub.rb +7 -5
  22. data/src/ruby/lib/grpc/generic/rpc_desc.rb +39 -13
  23. data/src/ruby/lib/grpc/generic/rpc_server.rb +51 -24
  24. data/src/ruby/lib/grpc/generic/service.rb +3 -2
  25. data/src/ruby/lib/grpc/version.rb +1 -1
  26. data/src/ruby/pb/grpc/health/checker.rb +3 -1
  27. data/src/ruby/pb/src/proto/grpc/testing/test_services_pb.rb +7 -0
  28. data/src/ruby/pb/test/client.rb +307 -7
  29. data/src/ruby/pb/test/server.rb +26 -1
  30. data/src/ruby/spec/compression_options_spec.rb +164 -0
  31. data/src/ruby/spec/error_sanity_spec.rb +64 -0
  32. data/src/ruby/spec/generic/active_call_spec.rb +290 -12
  33. data/src/ruby/spec/generic/client_stub_spec.rb +91 -41
  34. data/src/ruby/spec/generic/rpc_desc_spec.rb +36 -16
  35. data/src/ruby/spec/generic/rpc_server_pool_spec.rb +22 -28
  36. data/src/ruby/spec/generic/rpc_server_spec.rb +6 -6
  37. data/src/ruby/spec/pb/health/checker_spec.rb +27 -19
  38. data/src/ruby/spec/spec_helper.rb +2 -0
  39. metadata +18 -8
@@ -37,6 +37,7 @@
37
37
  #include "rb_server.h"
38
38
 
39
39
  #include <grpc/grpc.h>
40
+ #include <grpc/support/atm.h>
40
41
  #include <grpc/grpc_security.h>
41
42
  #include <grpc/support/log.h>
42
43
  #include "rb_call.h"
@@ -59,22 +60,26 @@ typedef struct grpc_rb_server {
59
60
  /* The actual server */
60
61
  grpc_server *wrapped;
61
62
  grpc_completion_queue *queue;
63
+ gpr_atm shutdown_started;
62
64
  } grpc_rb_server;
63
65
 
64
66
  static void destroy_server(grpc_rb_server *server, gpr_timespec deadline) {
65
67
  grpc_event ev;
66
- if (server->wrapped != NULL) {
67
- grpc_server_shutdown_and_notify(server->wrapped, server->queue, NULL);
68
- ev = rb_completion_queue_pluck(server->queue, NULL, deadline, NULL);
69
- if (ev.type == GRPC_QUEUE_TIMEOUT) {
70
- grpc_server_cancel_all_calls(server->wrapped);
71
- rb_completion_queue_pluck(server->queue, NULL,
72
- gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
68
+ // This can be started by app or implicitly by GC. Avoid a race between these.
69
+ if (gpr_atm_full_fetch_add(&server->shutdown_started, (gpr_atm)1) == 0) {
70
+ if (server->wrapped != NULL) {
71
+ grpc_server_shutdown_and_notify(server->wrapped, server->queue, NULL);
72
+ ev = rb_completion_queue_pluck(server->queue, NULL, deadline, NULL);
73
+ if (ev.type == GRPC_QUEUE_TIMEOUT) {
74
+ grpc_server_cancel_all_calls(server->wrapped);
75
+ rb_completion_queue_pluck(server->queue, NULL,
76
+ gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
77
+ }
78
+ grpc_server_destroy(server->wrapped);
79
+ grpc_rb_completion_queue_destroy(server->queue);
80
+ server->wrapped = NULL;
81
+ server->queue = NULL;
73
82
  }
74
- grpc_server_destroy(server->wrapped);
75
- grpc_rb_completion_queue_destroy(server->queue);
76
- server->wrapped = NULL;
77
- server->queue = NULL;
78
83
  }
79
84
  }
80
85
 
@@ -115,6 +120,7 @@ static const rb_data_type_t grpc_rb_server_data_type = {
115
120
  static VALUE grpc_rb_server_alloc(VALUE cls) {
116
121
  grpc_rb_server *wrapper = ALLOC(grpc_rb_server);
117
122
  wrapper->wrapped = NULL;
123
+ wrapper->shutdown_started = (gpr_atm)0;
118
124
  return TypedData_Wrap_Struct(cls, &grpc_rb_server_data_type, wrapper);
119
125
  }
120
126
 
@@ -218,7 +224,7 @@ static VALUE grpc_rb_server_request_call(VALUE self) {
218
224
  grpc_rb_sNewServerRpc, rb_str_new2(st.details.method),
219
225
  rb_str_new2(st.details.host),
220
226
  rb_funcall(rb_cTime, id_at, 2, INT2NUM(deadline.tv_sec),
221
- INT2NUM(deadline.tv_nsec)),
227
+ INT2NUM(deadline.tv_nsec / 1000)),
222
228
  grpc_rb_md_ary_to_h(&st.md_ary), grpc_rb_wrap_call(call, call_queue),
223
229
  NULL);
224
230
  grpc_request_call_stack_cleanup(&st);
@@ -35,9 +35,18 @@ module GRPC
35
35
  # either end of a GRPC connection. When raised, it indicates that a status
36
36
  # error should be returned to the other end of a GRPC connection; when
37
37
  # caught it means that this end received a status error.
38
+ #
39
+ # There is also subclass of BadStatus in this module for each GRPC status.
40
+ # E.g., the GRPC::Cancelled class corresponds to status CANCELLED.
41
+ #
42
+ # See
43
+ # https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/status.h
44
+ # for detailed descriptions of each status code.
38
45
  class BadStatus < StandardError
39
46
  attr_reader :code, :details, :metadata
40
47
 
48
+ include GRPC::Core::StatusCodes
49
+
41
50
  # @param code [Numeric] the status code
42
51
  # @param details [String] the details of the exception
43
52
  # @param metadata [Hash] the error's metadata
@@ -55,9 +64,152 @@ module GRPC
55
64
  def to_status
56
65
  Struct::Status.new(code, details, @metadata)
57
66
  end
67
+
68
+ def self.new_status_exception(code, details = 'unkown cause', metadata = {})
69
+ codes = {}
70
+ codes[OK] = Ok
71
+ codes[CANCELLED] = Cancelled
72
+ codes[UNKNOWN] = Unknown
73
+ codes[INVALID_ARGUMENT] = InvalidArgument
74
+ codes[DEADLINE_EXCEEDED] = DeadlineExceeded
75
+ codes[NOT_FOUND] = NotFound
76
+ codes[ALREADY_EXISTS] = AlreadyExists
77
+ codes[PERMISSION_DENIED] = PermissionDenied
78
+ codes[UNAUTHENTICATED] = Unauthenticated
79
+ codes[RESOURCE_EXHAUSTED] = ResourceExhausted
80
+ codes[FAILED_PRECONDITION] = FailedPrecondition
81
+ codes[ABORTED] = Aborted
82
+ codes[OUT_OF_RANGE] = OutOfRange
83
+ codes[UNIMPLEMENTED] = Unimplemented
84
+ codes[INTERNAL] = Internal
85
+ codes[UNIMPLEMENTED] = Unimplemented
86
+ codes[UNAVAILABLE] = Unavailable
87
+ codes[DATA_LOSS] = DataLoss
88
+
89
+ if codes[code].nil?
90
+ BadStatus.new(code, details, metadata)
91
+ else
92
+ codes[code].new(details, metadata)
93
+ end
94
+ end
95
+ end
96
+
97
+ # GRPC status code corresponding to status OK
98
+ class Ok < BadStatus
99
+ def initialize(details = 'unknown cause', metadata = {})
100
+ super(Core::StatusCodes::OK, details, metadata)
101
+ end
58
102
  end
59
103
 
60
- # Cancelled is an exception class that indicates that an rpc was cancelled.
61
- class Cancelled < StandardError
104
+ # GRPC status code corresponding to status CANCELLED
105
+ class Cancelled < BadStatus
106
+ def initialize(details = 'unknown cause', metadata = {})
107
+ super(Core::StatusCodes::CANCELLED, details, metadata)
108
+ end
109
+ end
110
+
111
+ # GRPC status code corresponding to status UNKNOWN
112
+ class Unknown < BadStatus
113
+ def initialize(details = 'unknown cause', metadata = {})
114
+ super(Core::StatusCodes::UNKNOWN, details, metadata)
115
+ end
116
+ end
117
+
118
+ # GRPC status code corresponding to status INVALID_ARGUMENT
119
+ class InvalidArgument < BadStatus
120
+ def initialize(details = 'unknown cause', metadata = {})
121
+ super(Core::StatusCodes::INVALID_ARGUMENT, details, metadata)
122
+ end
123
+ end
124
+
125
+ # GRPC status code corresponding to status DEADLINE_EXCEEDED
126
+ class DeadlineExceeded < BadStatus
127
+ def initialize(details = 'unknown cause', metadata = {})
128
+ super(Core::StatusCodes::DEADLINE_EXCEEDED, details, metadata)
129
+ end
130
+ end
131
+
132
+ # GRPC status code corresponding to status NOT_FOUND
133
+ class NotFound < BadStatus
134
+ def initialize(details = 'unknown cause', metadata = {})
135
+ super(Core::StatusCodes::NOT_FOUND, details, metadata)
136
+ end
137
+ end
138
+
139
+ # GRPC status code corresponding to status ALREADY_EXISTS
140
+ class AlreadyExists < BadStatus
141
+ def initialize(details = 'unknown cause', metadata = {})
142
+ super(Core::StatusCodes::ALREADY_EXISTS, details, metadata)
143
+ end
144
+ end
145
+
146
+ # GRPC status code corresponding to status PERMISSION_DENIED
147
+ class PermissionDenied < BadStatus
148
+ def initialize(details = 'unknown cause', metadata = {})
149
+ super(Core::StatusCodes::PERMISSION_DENIED, details, metadata)
150
+ end
151
+ end
152
+
153
+ # GRPC status code corresponding to status UNAUTHENTICATED
154
+ class Unauthenticated < BadStatus
155
+ def initialize(details = 'unknown cause', metadata = {})
156
+ super(Core::StatusCodes::UNAUTHENTICATED, details, metadata)
157
+ end
158
+ end
159
+
160
+ # GRPC status code corresponding to status RESOURCE_EXHAUSTED
161
+ class ResourceExhausted < BadStatus
162
+ def initialize(details = 'unknown cause', metadata = {})
163
+ super(Core::StatusCodes::RESOURCE_EXHAUSTED, details, metadata)
164
+ end
165
+ end
166
+
167
+ # GRPC status code corresponding to status FAILED_PRECONDITION
168
+ class FailedPrecondition < BadStatus
169
+ def initialize(details = 'unknown cause', metadata = {})
170
+ super(Core::StatusCodes::FAILED_PRECONDITION, details, metadata)
171
+ end
172
+ end
173
+
174
+ # GRPC status code corresponding to status ABORTED
175
+ class Aborted < BadStatus
176
+ def initialize(details = 'unknown cause', metadata = {})
177
+ super(Core::StatusCodes::ABORTED, details, metadata)
178
+ end
179
+ end
180
+
181
+ # GRPC status code corresponding to status OUT_OF_RANGE
182
+ class OutOfRange < BadStatus
183
+ def initialize(details = 'unknown cause', metadata = {})
184
+ super(Core::StatusCodes::OUT_OF_RANGE, details, metadata)
185
+ end
186
+ end
187
+
188
+ # GRPC status code corresponding to status UNIMPLEMENTED
189
+ class Unimplemented < BadStatus
190
+ def initialize(details = 'unknown cause', metadata = {})
191
+ super(Core::StatusCodes::UNIMPLEMENTED, details, metadata)
192
+ end
193
+ end
194
+
195
+ # GRPC status code corresponding to status INTERNAL
196
+ class Internal < BadStatus
197
+ def initialize(details = 'unknown cause', metadata = {})
198
+ super(Core::StatusCodes::INTERNAL, details, metadata)
199
+ end
200
+ end
201
+
202
+ # GRPC status code corresponding to status UNAVAILABLE
203
+ class Unavailable < BadStatus
204
+ def initialize(details = 'unknown cause', metadata = {})
205
+ super(Core::StatusCodes::UNAVAILABLE, details, metadata)
206
+ end
207
+ end
208
+
209
+ # GRPC status code corresponding to status DATA_LOSS
210
+ class DataLoss < BadStatus
211
+ def initialize(details = 'unknown cause', metadata = {})
212
+ super(Core::StatusCodes::DATA_LOSS, details, metadata)
213
+ end
62
214
  end
63
215
  end
@@ -43,7 +43,8 @@ class Struct
43
43
  GRPC.logger.debug("Failing with status #{status}")
44
44
  # raise BadStatus, propagating the metadata if present.
45
45
  md = status.metadata
46
- fail GRPC::BadStatus.new(status.code, status.details, md)
46
+ fail GRPC::BadStatus.new_status_exception(
47
+ status.code, status.details, md)
47
48
  end
48
49
  status
49
50
  end
@@ -58,7 +59,7 @@ module GRPC
58
59
  include Core::TimeConsts
59
60
  include Core::CallOps
60
61
  extend Forwardable
61
- attr_reader(:deadline)
62
+ attr_reader :deadline, :metadata_sent, :metadata_to_send
62
63
  def_delegators :@call, :cancel, :metadata, :write_flag, :write_flag=,
63
64
  :peer, :peer_cert, :trailing_metadata
64
65
 
@@ -101,7 +102,7 @@ module GRPC
101
102
  # @param metadata_received [true|false] indicates if metadata has already
102
103
  # been received. Should always be true for server calls
103
104
  def initialize(call, marshal, unmarshal, deadline, started: true,
104
- metadata_received: false)
105
+ metadata_received: false, metadata_to_send: nil)
105
106
  fail(TypeError, '!Core::Call') unless call.is_a? Core::Call
106
107
  @call = call
107
108
  @deadline = deadline
@@ -110,6 +111,20 @@ module GRPC
110
111
  @metadata_received = metadata_received
111
112
  @metadata_sent = started
112
113
  @op_notifier = nil
114
+
115
+ fail(ArgumentError, 'Already sent md') if started && metadata_to_send
116
+ @metadata_to_send = metadata_to_send || {} unless started
117
+ @send_initial_md_mutex = Mutex.new
118
+ end
119
+
120
+ # Sends the initial metadata that has yet to be sent.
121
+ # Does nothing if metadata has already been sent for this call.
122
+ def send_initial_metadata
123
+ @send_initial_md_mutex.synchronize do
124
+ return if @metadata_sent
125
+ @metadata_tag = ActiveCall.client_invoke(@call, @metadata_to_send)
126
+ @metadata_sent = true
127
+ end
113
128
  end
114
129
 
115
130
  # output_metadata are provides access to hash that can be used to
@@ -142,41 +157,25 @@ module GRPC
142
157
  Operation.new(self)
143
158
  end
144
159
 
145
- # writes_done indicates that all writes are completed.
146
- #
147
- # It blocks until the remote endpoint acknowledges with at status unless
148
- # assert_finished is set to false. Any calls to #remote_send after this
149
- # call will fail.
150
- #
151
- # @param assert_finished [true, false] when true(default), waits for
152
- # FINISHED.
153
- def writes_done(assert_finished = true)
154
- ops = {
155
- SEND_CLOSE_FROM_CLIENT => nil
156
- }
157
- ops[RECV_STATUS_ON_CLIENT] = nil if assert_finished
158
- batch_result = @call.run_batch(ops)
159
- return unless assert_finished
160
- unless batch_result.status.nil?
161
- @call.trailing_metadata = batch_result.status.metadata
162
- end
163
- @call.status = batch_result.status
164
- op_is_done
165
- batch_result.check_status
166
- end
167
-
168
160
  # finished waits until a client call is completed.
169
161
  #
170
162
  # It blocks until the remote endpoint acknowledges by sending a status.
171
163
  def finished
172
164
  batch_result = @call.run_batch(RECV_STATUS_ON_CLIENT => nil)
173
- unless batch_result.status.nil?
174
- @call.trailing_metadata = batch_result.status.metadata
165
+ attach_status_results_and_complete_call(batch_result)
166
+ end
167
+
168
+ def attach_status_results_and_complete_call(recv_status_batch_result)
169
+ unless recv_status_batch_result.status.nil?
170
+ @call.trailing_metadata = recv_status_batch_result.status.metadata
175
171
  end
176
- @call.status = batch_result.status
177
- op_is_done
178
- batch_result.check_status
172
+ @call.status = recv_status_batch_result.status
179
173
  @call.close
174
+ op_is_done
175
+
176
+ # The RECV_STATUS in run_batch always succeeds
177
+ # Check the status for a bad status or failed run batch
178
+ recv_status_batch_result.check_status
180
179
  end
181
180
 
182
181
  # remote_send sends a request to the remote endpoint.
@@ -187,7 +186,8 @@ module GRPC
187
186
  # @param marshalled [false, true] indicates if the object is already
188
187
  # marshalled.
189
188
  def remote_send(req, marshalled = false)
190
- # TODO(murgatroid99): ensure metadata was sent
189
+ send_initial_metadata
190
+ GRPC.logger.debug("sending #{req}, marshalled? #{marshalled}")
191
191
  payload = marshalled ? req : @marshal.call(req)
192
192
  @call.run_batch(SEND_MESSAGE => payload)
193
193
  end
@@ -202,6 +202,7 @@ module GRPC
202
202
  # list, mulitple metadata for its key are sent
203
203
  def send_status(code = OK, details = '', assert_finished = false,
204
204
  metadata: {})
205
+ send_initial_metadata
205
206
  ops = {
206
207
  SEND_STATUS_FROM_SERVER => Struct::Status.new(code, details, metadata)
207
208
  }
@@ -210,6 +211,23 @@ module GRPC
210
211
  nil
211
212
  end
212
213
 
214
+ def server_unary_response(req, trailing_metadata: {},
215
+ code: Core::StatusCodes::OK, details: 'OK')
216
+ ops = {}
217
+ @send_initial_md_mutex.synchronize do
218
+ ops[SEND_INITIAL_METADATA] = @metadata_to_send unless @metadata_sent
219
+ @metadata_sent = true
220
+ end
221
+
222
+ payload = @marshal.call(req)
223
+ ops[SEND_MESSAGE] = payload
224
+ ops[SEND_STATUS_FROM_SERVER] = Struct::Status.new(
225
+ code, details, trailing_metadata)
226
+ ops[RECV_CLOSE_ON_SERVER] = nil
227
+
228
+ @call.run_batch(ops)
229
+ end
230
+
213
231
  # remote_read reads a response from the remote endpoint.
214
232
  #
215
233
  # It blocks until the remote endpoint replies with a message or status.
@@ -224,9 +242,13 @@ module GRPC
224
242
  @call.metadata = batch_result.metadata
225
243
  @metadata_received = true
226
244
  end
227
- unless batch_result.nil? || batch_result.message.nil?
228
- res = @unmarshal.call(batch_result.message)
229
- return res
245
+ get_message_from_batch_result(batch_result)
246
+ end
247
+
248
+ def get_message_from_batch_result(recv_message_batch_result)
249
+ unless recv_message_batch_result.nil? ||
250
+ recv_message_batch_result.message.nil?
251
+ return @unmarshal.call(recv_message_batch_result.message)
230
252
  end
231
253
  GRPC.logger.debug('found nil; the final response has been sent')
232
254
  nil
@@ -282,7 +304,6 @@ module GRPC
282
304
  return enum_for(:each_remote_read_then_finish) unless block_given?
283
305
  loop do
284
306
  resp = remote_read
285
- break if resp.is_a? Struct::Status # is an OK status
286
307
  if resp.nil? # the last response was received, but not finished yet
287
308
  finished
288
309
  break
@@ -299,15 +320,25 @@ module GRPC
299
320
  # a list, multiple metadata for its key are sent
300
321
  # @return [Object] the response received from the server
301
322
  def request_response(req, metadata: {})
302
- start_call(metadata)
303
- remote_send(req)
304
- writes_done(false)
305
- response = remote_read
306
- finished unless response.is_a? Struct::Status
307
- response
308
- rescue GRPC::Core::CallError => e
309
- finished # checks for Cancelled
310
- raise e
323
+ ops = {
324
+ SEND_MESSAGE => @marshal.call(req),
325
+ SEND_CLOSE_FROM_CLIENT => nil,
326
+ RECV_INITIAL_METADATA => nil,
327
+ RECV_MESSAGE => nil,
328
+ RECV_STATUS_ON_CLIENT => nil
329
+ }
330
+ @send_initial_md_mutex.synchronize do
331
+ # Metadata might have already been sent if this is an operation view
332
+ unless @metadata_sent
333
+ ops[SEND_INITIAL_METADATA] = @metadata_to_send.merge!(metadata)
334
+ end
335
+ @metadata_sent = true
336
+ end
337
+ batch_result = @call.run_batch(ops)
338
+
339
+ @call.metadata = batch_result.metadata
340
+ attach_status_results_and_complete_call(batch_result)
341
+ get_message_from_batch_result(batch_result)
311
342
  end
312
343
 
313
344
  # client_streamer sends a stream of requests to a GRPC server, and
@@ -323,12 +354,20 @@ module GRPC
323
354
  # a list, multiple metadata for its key are sent
324
355
  # @return [Object] the response received from the server
325
356
  def client_streamer(requests, metadata: {})
326
- start_call(metadata)
327
- requests.each { |r| remote_send(r) }
328
- writes_done(false)
329
- response = remote_read
330
- finished unless response.is_a? Struct::Status
331
- response
357
+ # Metadata might have already been sent if this is an operation view
358
+ merge_metadata_and_send_if_not_already_sent(metadata)
359
+
360
+ requests.each { |r| @call.run_batch(SEND_MESSAGE => @marshal.call(r)) }
361
+ batch_result = @call.run_batch(
362
+ SEND_CLOSE_FROM_CLIENT => nil,
363
+ RECV_INITIAL_METADATA => nil,
364
+ RECV_MESSAGE => nil,
365
+ RECV_STATUS_ON_CLIENT => nil
366
+ )
367
+
368
+ @call.metadata = batch_result.metadata
369
+ attach_status_results_and_complete_call(batch_result)
370
+ get_message_from_batch_result(batch_result)
332
371
  rescue GRPC::Core::CallError => e
333
372
  finished # checks for Cancelled
334
373
  raise e
@@ -349,9 +388,18 @@ module GRPC
349
388
  # a list, multiple metadata for its key are sent
350
389
  # @return [Enumerator|nil] a response Enumerator
351
390
  def server_streamer(req, metadata: {})
352
- start_call(metadata)
353
- remote_send(req)
354
- writes_done(false)
391
+ ops = {
392
+ SEND_MESSAGE => @marshal.call(req),
393
+ SEND_CLOSE_FROM_CLIENT => nil
394
+ }
395
+ @send_initial_md_mutex.synchronize do
396
+ # Metadata might have already been sent if this is an operation view
397
+ unless @metadata_sent
398
+ ops[SEND_INITIAL_METADATA] = @metadata_to_send.merge!(metadata)
399
+ end
400
+ @metadata_sent = true
401
+ end
402
+ @call.run_batch(ops)
355
403
  replies = enum_for(:each_remote_read_then_finish)
356
404
  return replies unless block_given?
357
405
  replies.each { |r| yield r }
@@ -388,9 +436,13 @@ module GRPC
388
436
  # a list, multiple metadata for its key are sent
389
437
  # @return [Enumerator, nil] a response Enumerator
390
438
  def bidi_streamer(requests, metadata: {}, &blk)
391
- start_call(metadata)
392
- bd = BidiCall.new(@call, @marshal, @unmarshal,
439
+ # Metadata might have already been sent if this is an operation view
440
+ merge_metadata_and_send_if_not_already_sent(metadata)
441
+ bd = BidiCall.new(@call,
442
+ @marshal,
443
+ @unmarshal,
393
444
  metadata_received: @metadata_received)
445
+
394
446
  bd.run_on_client(requests, @op_notifier, &blk)
395
447
  end
396
448
 
@@ -406,8 +458,12 @@ module GRPC
406
458
  #
407
459
  # @param gen_each_reply [Proc] generates the BiDi stream replies
408
460
  def run_server_bidi(gen_each_reply)
409
- bd = BidiCall.new(@call, @marshal, @unmarshal,
410
- metadata_received: @metadata_received)
461
+ bd = BidiCall.new(@call,
462
+ @marshal,
463
+ @unmarshal,
464
+ metadata_received: @metadata_received,
465
+ req_view: MultiReqView.new(self))
466
+
411
467
  bd.run_on_server(gen_each_reply)
412
468
  end
413
469
 
@@ -424,15 +480,32 @@ module GRPC
424
480
  @op_notifier.notify(self)
425
481
  end
426
482
 
483
+ # Add to the metadata that will be sent from the server.
484
+ # Fails if metadata has already been sent.
485
+ # Unused by client calls.
486
+ def merge_metadata_to_send(new_metadata = {})
487
+ @send_initial_md_mutex.synchronize do
488
+ fail('cant change metadata after already sent') if @metadata_sent
489
+ @metadata_to_send.merge!(new_metadata)
490
+ end
491
+ end
492
+
493
+ def merge_metadata_and_send_if_not_already_sent(new_metadata = {})
494
+ @send_initial_md_mutex.synchronize do
495
+ return if @metadata_sent
496
+ @metadata_to_send.merge!(new_metadata)
497
+ @call.run_batch(SEND_INITIAL_METADATA => @metadata_to_send)
498
+ @metadata_sent = true
499
+ end
500
+ end
501
+
427
502
  private
428
503
 
429
504
  # Starts the call if not already started
430
505
  # @param metadata [Hash] metadata to be sent to the server. If a value is
431
506
  # a list, multiple metadata for its key are sent
432
507
  def start_call(metadata = {})
433
- return if @metadata_sent
434
- @metadata_tag = ActiveCall.client_invoke(@call, metadata)
435
- @metadata_sent = true
508
+ merge_metadata_to_send(metadata) && send_initial_metadata
436
509
  end
437
510
 
438
511
  def self.view_class(*visible_methods)
@@ -450,12 +523,20 @@ module GRPC
450
523
  # SingleReqView limits access to an ActiveCall's methods for use in server
451
524
  # handlers that receive just one request.
452
525
  SingleReqView = view_class(:cancelled?, :deadline, :metadata,
453
- :output_metadata, :peer, :peer_cert)
526
+ :output_metadata, :peer, :peer_cert,
527
+ :send_initial_metadata,
528
+ :metadata_to_send,
529
+ :merge_metadata_to_send,
530
+ :metadata_sent)
454
531
 
455
532
  # MultiReqView limits access to an ActiveCall's methods for use in
456
533
  # server client_streamer handlers.
457
534
  MultiReqView = view_class(:cancelled?, :deadline, :each_queued_msg,
458
- :each_remote_read, :metadata, :output_metadata)
535
+ :each_remote_read, :metadata, :output_metadata,
536
+ :send_initial_metadata,
537
+ :metadata_to_send,
538
+ :merge_metadata_to_send,
539
+ :metadata_sent)
459
540
 
460
541
  # Operation limits access to an ActiveCall's methods for use as
461
542
  # a Operation on the client.