grpc 0.6.0 → 0.6.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop_todo.yml +12 -20
  4. data/CHANGELOG.md +11 -0
  5. data/Rakefile +1 -0
  6. data/bin/apis/pubsub_demo.rb +3 -6
  7. data/bin/interop/interop_client.rb +43 -3
  8. data/bin/interop/interop_server.rb +1 -1
  9. data/bin/math_server.rb +1 -1
  10. data/bin/noproto_server.rb +1 -1
  11. data/ext/grpc/rb_byte_buffer.c +15 -189
  12. data/ext/grpc/rb_byte_buffer.h +4 -12
  13. data/ext/grpc/rb_call.c +514 -307
  14. data/ext/grpc/rb_call.h +4 -4
  15. data/ext/grpc/rb_channel.c +58 -34
  16. data/ext/grpc/rb_channel.h +0 -3
  17. data/ext/grpc/rb_channel_args.c +13 -4
  18. data/ext/grpc/rb_completion_queue.c +50 -23
  19. data/ext/grpc/rb_completion_queue.h +7 -3
  20. data/ext/grpc/rb_credentials.c +40 -28
  21. data/ext/grpc/rb_credentials.h +0 -4
  22. data/ext/grpc/rb_grpc.c +86 -67
  23. data/ext/grpc/rb_grpc.h +20 -10
  24. data/ext/grpc/rb_server.c +119 -26
  25. data/ext/grpc/rb_server.h +0 -4
  26. data/ext/grpc/rb_server_credentials.c +29 -16
  27. data/ext/grpc/rb_server_credentials.h +0 -4
  28. data/grpc.gemspec +11 -8
  29. data/lib/grpc.rb +1 -1
  30. data/lib/grpc/errors.rb +8 -7
  31. data/lib/grpc/generic/active_call.rb +104 -171
  32. data/lib/grpc/generic/bidi_call.rb +32 -60
  33. data/lib/grpc/generic/client_stub.rb +42 -31
  34. data/lib/grpc/generic/rpc_desc.rb +7 -12
  35. data/lib/grpc/generic/rpc_server.rb +253 -170
  36. data/lib/grpc/{core/event.rb → notifier.rb} +25 -9
  37. data/lib/grpc/version.rb +1 -1
  38. data/spec/call_spec.rb +23 -40
  39. data/spec/channel_spec.rb +11 -20
  40. data/spec/client_server_spec.rb +193 -175
  41. data/spec/credentials_spec.rb +2 -2
  42. data/spec/generic/active_call_spec.rb +59 -85
  43. data/spec/generic/client_stub_spec.rb +46 -64
  44. data/spec/generic/rpc_desc_spec.rb +50 -80
  45. data/spec/generic/rpc_server_pool_spec.rb +2 -3
  46. data/spec/generic/rpc_server_spec.rb +158 -29
  47. data/spec/server_spec.rb +1 -1
  48. data/spec/spec_helper.rb +8 -4
  49. metadata +27 -37
  50. data/ext/grpc/rb_event.c +0 -361
  51. data/ext/grpc/rb_event.h +0 -53
  52. data/ext/grpc/rb_metadata.c +0 -215
  53. data/ext/grpc/rb_metadata.h +0 -53
  54. data/spec/alloc_spec.rb +0 -44
  55. data/spec/byte_buffer_spec.rb +0 -67
  56. data/spec/event_spec.rb +0 -53
  57. data/spec/metadata_spec.rb +0 -64
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea942edb7ba32af1cbb7114b26097b3c08c70b90
4
- data.tar.gz: 434745623715052a46b4bcbe16166cc8dcc9d490
3
+ metadata.gz: 3fc4ef4584055918c9fb5118a4ae2be97fefd82b
4
+ data.tar.gz: b09b8e12d13fb1d46687a3738621ec863afa5abd
5
5
  SHA512:
6
- metadata.gz: 7eccf1a3db3deadf530258d2c14dd541886dbe6f4ef7ab29e46919cac883f85b5ce223b23665ab2ebd576d0b4316c28e5991303176b81aa9090a2b03f7e6347a
7
- data.tar.gz: 69d7a8a23039907725c62aed351208b2d45d3fc1b14b07971d3ff26c53854f0ee6417481547ed014dd07010397f4c4a46a90cd1b9880d87ada51edfaff0713f2
6
+ metadata.gz: b63ec97352b08405828884a32b6115c025b1507d0294b9553e5aa9bea6c9d6ff2d6f5651f13d85f50080a4888ee6976011a5b2e4c81147c93d1e08eead30d4a7
7
+ data.tar.gz: 9057402bc495a85cfd1970e1622a18031d089173cfbbfd82e691d56479cbbfddc34c3be6fe47c885c0383a1fa0e550129d7bad35415e8384789cd24aeb060ed2
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  -I.
2
+ --require spec_helper
@@ -1,42 +1,30 @@
1
1
  # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2015-01-16 02:30:04 -0800 using RuboCop version 0.28.0.
2
+ # on 2015-04-17 14:43:27 -0700 using RuboCop version 0.30.0.
3
3
  # The point is for the user to remove these configuration records
4
4
  # one by one as the offenses are removed from the code base.
5
5
  # Note that changes in the inspected code, or installation of new
6
6
  # versions of RuboCop, may require this file to be generated again.
7
7
 
8
- # Offense count: 3
9
- # Lint/UselessAssignment:
10
- # Enabled: false
11
-
12
- # Offense count: 33
8
+ # Offense count: 30
13
9
  Metrics/AbcSize:
14
- Max: 39
10
+ Max: 40
15
11
 
16
12
  # Offense count: 3
17
13
  # Configuration parameters: CountComments.
18
14
  Metrics/ClassLength:
19
- Max: 231
20
-
21
- # Offense count: 2
22
- Metrics/CyclomaticComplexity:
23
- Max: 8
15
+ Max: 184
24
16
 
25
- # Offense count: 36
17
+ # Offense count: 35
26
18
  # Configuration parameters: CountComments.
27
19
  Metrics/MethodLength:
28
- Max: 37
20
+ Max: 36
29
21
 
30
- # Offense count: 8
22
+ # Offense count: 7
31
23
  # Configuration parameters: CountKeywordArgs.
32
24
  Metrics/ParameterLists:
33
25
  Max: 8
34
26
 
35
- # Offense count: 2
36
- Metrics/PerceivedComplexity:
37
- Max: 10
38
-
39
- # Offense count: 7
27
+ # Offense count: 9
40
28
  # Configuration parameters: AllowedVariables.
41
29
  Style/GlobalVars:
42
30
  Enabled: false
@@ -50,3 +38,7 @@ Style/Next:
50
38
  # Configuration parameters: Methods.
51
39
  Style/SingleLineBlockParams:
52
40
  Enabled: false
41
+
42
+ # Offense count: 1
43
+ Style/StructInheritance:
44
+ Enabled: false
@@ -0,0 +1,11 @@
1
+ ## 0.6.1 (2015-04-14)
2
+
3
+ ### Changes
4
+
5
+ * Begins this ChangeLog ([@tbetbetbe][])
6
+ * Updates to version 0.4 of googleauth. ([@tbetbetbe][])
7
+ * Switch the extension to use the call API. ([@tbetbetbe][])
8
+ * Refactor the C extension to avoid identifiers used by ruby ([@yugui][])
9
+
10
+ [@tbetbetbe]: https://github.com/tbetbetbe
11
+ [@yugui]: https://github.com/yugui
data/Rakefile CHANGED
@@ -26,6 +26,7 @@ namespace :suite do
26
26
  SPEC_SUITES.each do |suite|
27
27
  desc "Run all specs in the #{suite[:title]} spec suite"
28
28
  RSpec::Core::RakeTask.new(suite[:id]) do |t|
29
+ ENV['COVERAGE_NAME'] = suite[:id].to_s
29
30
  spec_files = []
30
31
  suite[:files].each { |f| spec_files += Dir[f] } if suite[:files]
31
32
 
@@ -71,7 +71,7 @@ end
71
71
 
72
72
  # Builds the metadata authentication update proc.
73
73
  def auth_proc(opts)
74
- auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
74
+ auth_creds = Google::Auth.get_application_default
75
75
  return auth_creds.updater_proc
76
76
  end
77
77
 
@@ -213,17 +213,14 @@ class NamedActions
213
213
  end
214
214
 
215
215
  # Args is used to hold the command line info.
216
- Args = Struct.new(:host, :oauth_scope, :port, :action, :project_id, :topic_name,
216
+ Args = Struct.new(:host, :port, :action, :project_id, :topic_name,
217
217
  :sub_name)
218
218
 
219
219
  # validates the the command line options, returning them as an Arg.
220
220
  def parse_args
221
221
  args = Args.new('pubsub-staging.googleapis.com',
222
- 'https://www.googleapis.com/auth/pubsub',
223
222
  443, 'list_some_topics', 'stoked-keyword-656')
224
223
  OptionParser.new do |opts|
225
- opts.on('--oauth_scope scope',
226
- 'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
227
224
  opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
228
225
  args.host = v
229
226
  end
@@ -250,7 +247,7 @@ def parse_args
250
247
  end
251
248
 
252
249
  def _check_args(args)
253
- %w(host port action oauth_scope).each do |a|
250
+ %w(host port action).each do |a|
254
251
  if args[a].nil?
255
252
  raise OptionParser::MissingArgument.new("please specify --#{a}")
256
253
  end
@@ -110,6 +110,11 @@ def create_stub(opts)
110
110
  end
111
111
  end
112
112
 
113
+ if opts.test_case == 'jwt_token_creds' # don't use a scope
114
+ auth_creds = Google::Auth.get_application_default
115
+ stub_opts[:update_metadata] = auth_creds.updater_proc
116
+ end
117
+
113
118
  logger.info("... connecting securely to #{address}")
114
119
  Grpc::Testing::TestService::Stub.new(address, **stub_opts)
115
120
  else
@@ -131,12 +136,14 @@ class PingPongPlayer
131
136
  include Grpc::Testing::PayloadType
132
137
  attr_accessor :assertions # required by Minitest::Assertions
133
138
  attr_accessor :queue
139
+ attr_accessor :canceller_op
134
140
 
135
141
  # reqs is the enumerator over the requests
136
142
  def initialize(msg_sizes)
137
143
  @queue = Queue.new
138
144
  @msg_sizes = msg_sizes
139
145
  @assertions = 0 # required by Minitest::Assertions
146
+ @canceller_op = nil # used to cancel after the first response
140
147
  end
141
148
 
142
149
  def each_item
@@ -150,12 +157,15 @@ class PingPongPlayer
150
157
  response_parameters: [p_cls.new(size: resp_size)])
151
158
  yield req
152
159
  resp = @queue.pop
153
- assert_equal(:COMPRESSABLE, resp.payload.type,
154
- 'payload type is wrong')
160
+ assert_equal(:COMPRESSABLE, resp.payload.type, 'payload type is wrong')
155
161
  assert_equal(resp_size, resp.payload.body.length,
156
- 'payload body #{i} has the wrong length')
162
+ "payload body #{count} has the wrong length")
157
163
  p "OK: ping_pong #{count}"
158
164
  count += 1
165
+ unless @canceller_op.nil?
166
+ canceller_op.cancel
167
+ break
168
+ end
159
169
  end
160
170
  end
161
171
  end
@@ -201,6 +211,15 @@ class NamedTests
201
211
  p 'OK: service_account_creds'
202
212
  end
203
213
 
214
+ def jwt_token_creds
215
+ json_key = File.read(ENV[AUTH_ENV])
216
+ wanted_email = MultiJson.load(json_key)['client_email']
217
+ resp = perform_large_unary(fill_username: true)
218
+ assert_equal(wanted_email, resp.username,
219
+ 'service_account_creds: incorrect username')
220
+ p 'OK: jwt_token_creds'
221
+ end
222
+
204
223
  def compute_engine_creds
205
224
  resp = perform_large_unary(fill_username: true,
206
225
  fill_oauth_scope: true)
@@ -246,6 +265,27 @@ class NamedTests
246
265
  p 'OK: ping_pong'
247
266
  end
248
267
 
268
+ def cancel_after_begin
269
+ msg_sizes = [27_182, 8, 1828, 45_904]
270
+ reqs = msg_sizes.map do |x|
271
+ req = Payload.new(body: nulls(x))
272
+ StreamingInputCallRequest.new(payload: req)
273
+ end
274
+ op = @stub.streaming_input_call(reqs, return_op: true)
275
+ op.cancel
276
+ assert_raises(GRPC::Cancelled) { op.execute }
277
+ p 'OK: cancel_after_begin'
278
+ end
279
+
280
+ def cancel_after_first_response
281
+ msg_sizes = [[27_182, 31_415], [8, 9], [1828, 2653], [45_904, 58_979]]
282
+ ppp = PingPongPlayer.new(msg_sizes)
283
+ op = @stub.full_duplex_call(ppp.each_item, return_op: true)
284
+ ppp.canceller_op = op # causes ppp to cancel after the 1st message
285
+ assert_raises(GRPC::Cancelled) { op.execute.each { |r| ppp.queue.push(r) } }
286
+ p 'OK: cancel_after_first_response'
287
+ end
288
+
249
289
  def all
250
290
  all_methods = NamedTests.instance_methods(false).map(&:to_s)
251
291
  all_methods.each do |m|
@@ -185,7 +185,7 @@ def main
185
185
  logger.info("... running insecurely on #{host}")
186
186
  end
187
187
  s.handle(TestTarget)
188
- s.run
188
+ s.run_till_terminated
189
189
  end
190
190
 
191
191
  main
@@ -183,7 +183,7 @@ def main
183
183
  end
184
184
 
185
185
  s.handle(Calculator)
186
- s.run
186
+ s.run_till_terminated
187
187
  end
188
188
 
189
189
  main
@@ -105,7 +105,7 @@ def main
105
105
  end
106
106
 
107
107
  s.handle(NoProto)
108
- s.run
108
+ s.run_till_terminated
109
109
  end
110
110
 
111
111
  main
@@ -39,203 +39,29 @@
39
39
  #include <grpc/support/slice.h>
40
40
  #include "rb_grpc.h"
41
41
 
42
- /* grpc_rb_byte_buffer wraps a grpc_byte_buffer. It provides a peer ruby
43
- * object, 'mark' to minimize copying when a byte_buffer is created from
44
- * ruby. */
45
- typedef struct grpc_rb_byte_buffer {
46
- /* Holder of ruby objects involved in constructing the status */
47
- VALUE mark;
48
- /* The actual status */
49
- grpc_byte_buffer *wrapped;
50
- } grpc_rb_byte_buffer;
51
-
52
- /* Destroys ByteBuffer instances. */
53
- static void grpc_rb_byte_buffer_free(void *p) {
54
- grpc_rb_byte_buffer *bb = NULL;
55
- if (p == NULL) {
56
- return;
57
- };
58
- bb = (grpc_rb_byte_buffer *)p;
59
-
60
- /* Deletes the wrapped object if the mark object is Qnil, which indicates
61
- * that no other object is the actual owner. */
62
- if (bb->wrapped != NULL && bb->mark == Qnil) {
63
- grpc_byte_buffer_destroy(bb->wrapped);
64
- }
65
-
66
- xfree(p);
67
- }
68
-
69
- /* Protects the mark object from GC */
70
- static void grpc_rb_byte_buffer_mark(void *p) {
71
- grpc_rb_byte_buffer *bb = NULL;
72
- if (p == NULL) {
73
- return;
74
- }
75
- bb = (grpc_rb_byte_buffer *)p;
76
-
77
- /* If it's not already cleaned up, mark the mark object */
78
- if (bb->mark != Qnil && BUILTIN_TYPE(bb->mark) != T_NONE) {
79
- rb_gc_mark(bb->mark);
80
- }
42
+ grpc_byte_buffer* grpc_rb_s_to_byte_buffer(char *string, size_t length) {
43
+ gpr_slice slice = gpr_slice_from_copied_buffer(string, length);
44
+ grpc_byte_buffer *buffer = grpc_byte_buffer_create(&slice, 1);
45
+ gpr_slice_unref(slice);
46
+ return buffer;
81
47
  }
82
48
 
83
- /* id_source is the name of the hidden ivar the preserves the original
84
- * byte_buffer source string */
85
- static ID id_source;
86
-
87
- /* Allocates ByteBuffer instances.
88
-
89
- Provides safe default values for the byte_buffer fields. */
90
- static VALUE grpc_rb_byte_buffer_alloc(VALUE cls) {
91
- grpc_rb_byte_buffer *wrapper = ALLOC(grpc_rb_byte_buffer);
92
- wrapper->wrapped = NULL;
93
- wrapper->mark = Qnil;
94
- return Data_Wrap_Struct(cls, grpc_rb_byte_buffer_mark,
95
- grpc_rb_byte_buffer_free, wrapper);
96
- }
97
-
98
- /* Clones ByteBuffer instances.
99
-
100
- Gives ByteBuffer a consistent implementation of Ruby's object copy/dup
101
- protocol. */
102
- static VALUE grpc_rb_byte_buffer_init_copy(VALUE copy, VALUE orig) {
103
- grpc_rb_byte_buffer *orig_bb = NULL;
104
- grpc_rb_byte_buffer *copy_bb = NULL;
105
-
106
- if (copy == orig) {
107
- return copy;
108
- }
109
-
110
- /* Raise an error if orig is not a metadata object or a subclass. */
111
- if (TYPE(orig) != T_DATA ||
112
- RDATA(orig)->dfree != (RUBY_DATA_FUNC)grpc_rb_byte_buffer_free) {
113
- rb_raise(rb_eTypeError, "not a %s", rb_obj_classname(rb_cByteBuffer));
114
- }
115
-
116
- Data_Get_Struct(orig, grpc_rb_byte_buffer, orig_bb);
117
- Data_Get_Struct(copy, grpc_rb_byte_buffer, copy_bb);
118
-
119
- /* use ruby's MEMCPY to make a byte-for-byte copy of the metadata wrapper
120
- * object. */
121
- MEMCPY(copy_bb, orig_bb, grpc_rb_byte_buffer, 1);
122
- return copy;
123
- }
124
-
125
- /* id_empty is used to return the empty string from to_s when necessary. */
126
- static ID id_empty;
127
-
128
- static VALUE grpc_rb_byte_buffer_to_s(VALUE self) {
129
- grpc_rb_byte_buffer *wrapper = NULL;
130
- grpc_byte_buffer *bb = NULL;
131
- grpc_byte_buffer_reader *reader = NULL;
132
- char *output = NULL;
49
+ VALUE grpc_rb_byte_buffer_to_s(grpc_byte_buffer *buffer) {
133
50
  size_t length = 0;
51
+ char *string = NULL;
134
52
  size_t offset = 0;
135
- VALUE output_obj = Qnil;
53
+ grpc_byte_buffer_reader *reader = NULL;
136
54
  gpr_slice next;
55
+ if (buffer == NULL) {
56
+ return Qnil;
137
57
 
138
- Data_Get_Struct(self, grpc_rb_byte_buffer, wrapper);
139
- output_obj = rb_ivar_get(wrapper->mark, id_source);
140
- if (output_obj != Qnil) {
141
- /* From ruby, ByteBuffers are immutable so if a source is set, return that
142
- * as the to_s value */
143
- return output_obj;
144
- }
145
-
146
- /* Read the bytes. */
147
- bb = wrapper->wrapped;
148
- if (bb == NULL) {
149
- return rb_id2str(id_empty);
150
- }
151
- length = grpc_byte_buffer_length(bb);
152
- if (length == 0) {
153
- return rb_id2str(id_empty);
154
58
  }
155
- reader = grpc_byte_buffer_reader_create(bb);
156
- output = xmalloc(length);
59
+ length = grpc_byte_buffer_length(buffer);
60
+ string = xmalloc(length + 1);
61
+ reader = grpc_byte_buffer_reader_create(buffer);
157
62
  while (grpc_byte_buffer_reader_next(reader, &next) != 0) {
158
- memcpy(output + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
63
+ memcpy(string + offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
159
64
  offset += GPR_SLICE_LENGTH(next);
160
65
  }
161
- output_obj = rb_str_new(output, length);
162
-
163
- /* Save a references to the computed string in the mark object so that the
164
- * calling to_s does not do any allocations. */
165
- wrapper->mark = rb_class_new_instance(0, NULL, rb_cObject);
166
- rb_ivar_set(wrapper->mark, id_source, output_obj);
167
-
168
- return output_obj;
169
- }
170
-
171
- /* Initializes ByteBuffer instances. */
172
- static VALUE grpc_rb_byte_buffer_init(VALUE self, VALUE src) {
173
- gpr_slice a_slice;
174
- grpc_rb_byte_buffer *wrapper = NULL;
175
- grpc_byte_buffer *byte_buffer = NULL;
176
-
177
- if (TYPE(src) != T_STRING) {
178
- rb_raise(rb_eTypeError, "bad byte_buffer arg: got <%s>, want <String>",
179
- rb_obj_classname(src));
180
- return Qnil;
181
- }
182
- Data_Get_Struct(self, grpc_rb_byte_buffer, wrapper);
183
- a_slice = gpr_slice_malloc(RSTRING_LEN(src));
184
- memcpy(GPR_SLICE_START_PTR(a_slice), RSTRING_PTR(src), RSTRING_LEN(src));
185
- byte_buffer = grpc_byte_buffer_create(&a_slice, 1);
186
- gpr_slice_unref(a_slice);
187
-
188
- if (byte_buffer == NULL) {
189
- rb_raise(rb_eArgError, "could not create a byte_buffer, not sure why");
190
- return Qnil;
191
- }
192
- wrapper->wrapped = byte_buffer;
193
-
194
- /* Save a references to the original string in the mark object so that the
195
- * pointers used there is valid for the lifetime of the object. */
196
- wrapper->mark = rb_class_new_instance(0, NULL, rb_cObject);
197
- rb_ivar_set(wrapper->mark, id_source, src);
198
-
199
- return self;
200
- }
201
-
202
- /* rb_cByteBuffer is the ruby class that proxies grpc_byte_buffer. */
203
- VALUE rb_cByteBuffer = Qnil;
204
-
205
- void Init_grpc_byte_buffer() {
206
- rb_cByteBuffer =
207
- rb_define_class_under(rb_mGrpcCore, "ByteBuffer", rb_cObject);
208
-
209
- /* Allocates an object managed by the ruby runtime */
210
- rb_define_alloc_func(rb_cByteBuffer, grpc_rb_byte_buffer_alloc);
211
-
212
- /* Provides a ruby constructor and support for dup/clone. */
213
- rb_define_method(rb_cByteBuffer, "initialize", grpc_rb_byte_buffer_init, 1);
214
- rb_define_method(rb_cByteBuffer, "initialize_copy",
215
- grpc_rb_byte_buffer_init_copy, 1);
216
-
217
- /* Provides a to_s method that returns the buffer value */
218
- rb_define_method(rb_cByteBuffer, "to_s", grpc_rb_byte_buffer_to_s, 0);
219
-
220
- id_source = rb_intern("__source");
221
- id_empty = rb_intern("");
222
- }
223
-
224
- VALUE grpc_rb_byte_buffer_create_with_mark(VALUE mark, grpc_byte_buffer *bb) {
225
- grpc_rb_byte_buffer *byte_buffer = NULL;
226
- if (bb == NULL) {
227
- return Qnil;
228
- }
229
- byte_buffer = ALLOC(grpc_rb_byte_buffer);
230
- byte_buffer->wrapped = bb;
231
- byte_buffer->mark = mark;
232
- return Data_Wrap_Struct(rb_cByteBuffer, grpc_rb_byte_buffer_mark,
233
- grpc_rb_byte_buffer_free, byte_buffer);
234
- }
235
-
236
- /* Gets the wrapped byte_buffer from the ruby wrapper */
237
- grpc_byte_buffer *grpc_rb_get_wrapped_byte_buffer(VALUE v) {
238
- grpc_rb_byte_buffer *wrapper = NULL;
239
- Data_Get_Struct(v, grpc_rb_byte_buffer, wrapper);
240
- return wrapper->wrapped;
66
+ return rb_str_new(string, length);
241
67
  }