msgpack 1.4.5 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,6 +33,10 @@ static VALUE eUnexpectedTypeError;
33
33
  static VALUE eUnknownExtTypeError;
34
34
  static VALUE mTypeError; // obsoleted. only for backward compatibility. See #86.
35
35
 
36
+ static VALUE sym_symbolize_keys;
37
+ static VALUE sym_freeze;
38
+ static VALUE sym_allow_unknown_ext;
39
+
36
40
  #define UNPACKER(from, name) \
37
41
  msgpack_unpacker_t *name = NULL; \
38
42
  Data_Get_Struct(from, msgpack_unpacker_t, name); \
@@ -83,7 +87,7 @@ VALUE MessagePack_Unpacker_initialize(int argc, VALUE* argv, VALUE self)
83
87
  } else if(argc == 2) {
84
88
  io = argv[0];
85
89
  options = argv[1];
86
- if(rb_type(options) != T_HASH) {
90
+ if(options != Qnil && rb_type(options) != T_HASH) {
87
91
  rb_raise(rb_eArgError, "expected Hash but found %s.", rb_obj_classname(options));
88
92
  }
89
93
 
@@ -100,13 +104,13 @@ VALUE MessagePack_Unpacker_initialize(int argc, VALUE* argv, VALUE self)
100
104
  if(options != Qnil) {
101
105
  VALUE v;
102
106
 
103
- v = rb_hash_aref(options, ID2SYM(rb_intern("symbolize_keys")));
107
+ v = rb_hash_aref(options, sym_symbolize_keys);
104
108
  msgpack_unpacker_set_symbolized_keys(uk, RTEST(v));
105
109
 
106
- v = rb_hash_aref(options, ID2SYM(rb_intern("freeze")));
110
+ v = rb_hash_aref(options, sym_freeze);
107
111
  msgpack_unpacker_set_freeze(uk, RTEST(v));
108
112
 
109
- v = rb_hash_aref(options, ID2SYM(rb_intern("allow_unknown_ext")));
113
+ v = rb_hash_aref(options, sym_allow_unknown_ext);
110
114
  msgpack_unpacker_set_allow_unknown_ext(uk, RTEST(v));
111
115
  }
112
116
 
@@ -143,6 +147,7 @@ NORETURN(static void raise_unpacker_error(int r))
143
147
  case PRIMITIVE_UNEXPECTED_TYPE:
144
148
  rb_raise(eUnexpectedTypeError, "unexpected type");
145
149
  case PRIMITIVE_UNEXPECTED_EXT_TYPE:
150
+ // rb_bug("unexpected extension type");
146
151
  rb_raise(eUnknownExtTypeError, "unexpected extension type");
147
152
  default:
148
153
  rb_raise(eUnpackError, "logically unknown error %d", r);
@@ -361,7 +366,7 @@ static VALUE Unpacker_register_type(int argc, VALUE* argv, VALUE self)
361
366
  rb_raise(rb_eRangeError, "integer %d too big to convert to `signed char'", ext_type);
362
367
  }
363
368
 
364
- msgpack_unpacker_ext_registry_put(&uk->ext_registry, ext_module, ext_type, proc, arg);
369
+ msgpack_unpacker_ext_registry_put(&uk->ext_registry, ext_module, ext_type, 0, proc, arg);
365
370
 
366
371
  return Qnil;
367
372
  }
@@ -411,6 +416,10 @@ void MessagePack_Unpacker_module_init(VALUE mMessagePack)
411
416
 
412
417
  eUnknownExtTypeError = rb_define_class_under(mMessagePack, "UnknownExtTypeError", eUnpackError);
413
418
 
419
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
420
+ sym_freeze = ID2SYM(rb_intern("freeze"));
421
+ sym_allow_unknown_ext = ID2SYM(rb_intern("allow_unknown_ext"));
422
+
414
423
  rb_define_alloc_func(cMessagePack_Unpacker, MessagePack_Unpacker_alloc);
415
424
 
416
425
  rb_define_method(cMessagePack_Unpacker, "initialize", MessagePack_Unpacker_initialize, -1);
@@ -77,9 +77,10 @@ void msgpack_unpacker_ext_registry_release(msgpack_unpacker_ext_registry_t* ukrg
77
77
  }
78
78
 
79
79
  void msgpack_unpacker_ext_registry_put(msgpack_unpacker_ext_registry_t** ukrg,
80
- VALUE ext_module, int ext_type, VALUE proc, VALUE arg)
80
+ VALUE ext_module, int ext_type, int flags, VALUE proc, VALUE arg)
81
81
  {
82
82
  msgpack_unpacker_ext_registry_t* ext_registry = msgpack_unpacker_ext_registry_cow(*ukrg);
83
- ext_registry->array[ext_type + 128] = rb_ary_new3(3, ext_module, proc, arg);
83
+
84
+ ext_registry->array[ext_type + 128] = rb_ary_new3(4, ext_module, proc, arg, INT2FIX(flags));
84
85
  *ukrg = ext_registry;
85
86
  }
@@ -21,6 +21,8 @@
21
21
  #include "compat.h"
22
22
  #include "ruby.h"
23
23
 
24
+ #define MSGPACK_EXT_RECURSIVE 0b0001
25
+
24
26
  struct msgpack_unpacker_ext_registry_t;
25
27
  typedef struct msgpack_unpacker_ext_registry_t msgpack_unpacker_ext_registry_t;
26
28
 
@@ -46,14 +48,15 @@ static inline void msgpack_unpacker_ext_registry_borrow(msgpack_unpacker_ext_reg
46
48
  void msgpack_unpacker_ext_registry_mark(msgpack_unpacker_ext_registry_t* ukrg);
47
49
 
48
50
  void msgpack_unpacker_ext_registry_put(msgpack_unpacker_ext_registry_t** ukrg,
49
- VALUE ext_module, int ext_type, VALUE proc, VALUE arg);
51
+ VALUE ext_module, int ext_type, int flags, VALUE proc, VALUE arg);
50
52
 
51
53
  static inline VALUE msgpack_unpacker_ext_registry_lookup(msgpack_unpacker_ext_registry_t* ukrg,
52
- int ext_type)
54
+ int ext_type, int* ext_flags_result)
53
55
  {
54
56
  if (ukrg) {
55
57
  VALUE entry = ukrg->array[ext_type + 128];
56
58
  if (entry != Qnil) {
59
+ *ext_flags_result = FIX2INT(rb_ary_entry(entry, 3));
57
60
  return rb_ary_entry(entry, 1);
58
61
  }
59
62
  }
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MessagePack
4
+ module Bigint
5
+ # We split the bigint in 32bits chunks so that individual part fits into
6
+ # a MRI immediate Integer.
7
+ CHUNK_BITLENGTH = 32
8
+ FORMAT = 'CL>*'
9
+
10
+ if Integer.instance_method(:[]).arity != 1 # Ruby 2.7 and newer
11
+ # Starting from Ruby 2.7 we can address arbitrary bitranges inside an Integer with Integer#[]
12
+ # This allows to not allocate any Integer.
13
+ def self.to_msgpack_ext(bigint)
14
+ members = []
15
+
16
+ if bigint < 0
17
+ bigint = -bigint
18
+ members << 1
19
+ else
20
+ members << 0
21
+ end
22
+
23
+ offset = 0
24
+ length = bigint.bit_length
25
+ while offset < length
26
+ members << bigint[offset, CHUNK_BITLENGTH]
27
+ offset += CHUNK_BITLENGTH
28
+ end
29
+
30
+ members.pack(FORMAT)
31
+ end
32
+ else
33
+ # On 2.6 and older since we can't address arbitrary bitranges, so we fallback to shifting the bigint.
34
+ # This means that after each shift, we may allocate another Integer instance.
35
+ BASE = (2**CHUNK_BITLENGTH) - 1
36
+ def self.to_msgpack_ext(bigint)
37
+ members = []
38
+
39
+ if bigint < 0
40
+ bigint = -bigint
41
+ members << 1
42
+ else
43
+ members << 0
44
+ end
45
+
46
+ while bigint > 0
47
+ members << (bigint & BASE)
48
+ bigint = bigint >> CHUNK_BITLENGTH
49
+ end
50
+
51
+ members.pack(FORMAT)
52
+ end
53
+ end
54
+
55
+ def self.from_msgpack_ext(data)
56
+ parts = data.unpack(FORMAT)
57
+
58
+ sign = parts.shift
59
+ sum = parts.pop.to_i
60
+
61
+ parts.reverse_each do |part|
62
+ sum = sum << CHUNK_BITLENGTH
63
+ sum += part
64
+ end
65
+
66
+ sign == 0 ? sum : -sum
67
+ end
68
+ end
69
+ end
@@ -77,5 +77,108 @@ module MessagePack
77
77
  packer.full_pack
78
78
  end
79
79
  alias :pack :dump
80
+
81
+ def pool(size = 1, **options)
82
+ Pool.new(
83
+ frozen? ? self : dup.freeze,
84
+ size,
85
+ options.empty? ? nil : options,
86
+ )
87
+ end
88
+
89
+ class Pool
90
+ if RUBY_ENGINE == "ruby"
91
+ class AbstractPool
92
+ def initialize(size, &block)
93
+ @size = size
94
+ @new_member = block
95
+ @members = []
96
+ end
97
+
98
+ def checkout
99
+ @members.pop || @new_member.call
100
+ end
101
+
102
+ def checkin(member)
103
+ # If the pool is already full, we simply drop the extra member.
104
+ # This is because contrary to a connection pool, creating an extra instance
105
+ # is extremely unlikely to cause some kind of resource exhaustion.
106
+ #
107
+ # We could cycle the members (keep the newer one) but first It's more work and second
108
+ # the older member might have been created pre-fork, so it might be at least partially
109
+ # in shared memory.
110
+ if member && @members.size < @size
111
+ member.reset
112
+ @members << member
113
+ end
114
+ end
115
+ end
116
+ else
117
+ class AbstractPool
118
+ def initialize(size, &block)
119
+ @size = size
120
+ @new_member = block
121
+ @members = []
122
+ @mutex = Mutex.new
123
+ end
124
+
125
+ def checkout
126
+ @mutex.synchronize { @members.pop } || @new_member.call
127
+ end
128
+
129
+ def checkin(member)
130
+ @mutex.synchronize do
131
+ if member && @members.size < @size
132
+ member.reset
133
+ @members << member
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ class PackerPool < AbstractPool
141
+ private
142
+
143
+ def reset(packer)
144
+ packer.clear
145
+ end
146
+ end
147
+
148
+ class UnpackerPool < AbstractPool
149
+ private
150
+
151
+ def reset(unpacker)
152
+ unpacker.reset
153
+ end
154
+ end
155
+
156
+ def initialize(factory, size, options = nil)
157
+ options = nil if !options || options.empty?
158
+ @factory = factory
159
+ @packers = PackerPool.new(size) { factory.packer(options) }
160
+ @unpackers = UnpackerPool.new(size) { factory.unpacker(options) }
161
+ end
162
+
163
+ def load(data)
164
+ unpacker = @unpackers.checkout
165
+ begin
166
+ unpacker.feed_reference(data)
167
+ unpacker.full_unpack
168
+ ensure
169
+ @unpackers.checkin(unpacker)
170
+ end
171
+ end
172
+
173
+ def dump(object)
174
+ packer = @packers.checkout
175
+ begin
176
+ packer.write(object)
177
+ packer.full_pack
178
+ ensure
179
+ @packers.checkin(packer)
180
+ end
181
+ end
182
+ end
80
183
  end
81
184
  end
@@ -1,5 +1,5 @@
1
1
  module MessagePack
2
- VERSION = "1.4.5"
2
+ VERSION = "1.5.0"
3
3
  # Note for maintainers:
4
4
  # Don't miss building/releasing the JRuby version (rake buld:java)
5
5
  # See "How to build -java rubygems" in README for more details.
data/lib/msgpack.rb CHANGED
@@ -17,16 +17,15 @@ require "msgpack/time"
17
17
 
18
18
  module MessagePack
19
19
  DefaultFactory = MessagePack::Factory.new
20
- DEFAULT_EMPTY_PARAMS = {}.freeze
21
20
 
22
21
  def load(src, param = nil)
23
22
  unpacker = nil
24
23
 
25
24
  if src.is_a? String
26
- unpacker = DefaultFactory.unpacker param || DEFAULT_EMPTY_PARAMS
25
+ unpacker = DefaultFactory.unpacker param
27
26
  unpacker.feed_reference src
28
27
  else
29
- unpacker = DefaultFactory.unpacker src, param || DEFAULT_EMPTY_PARAMS
28
+ unpacker = DefaultFactory.unpacker src, param
30
29
  end
31
30
 
32
31
  unpacker.full_unpack
@@ -36,8 +35,8 @@ module MessagePack
36
35
  module_function :load
37
36
  module_function :unpack
38
37
 
39
- def pack(v, *rest)
40
- packer = DefaultFactory.packer(*rest)
38
+ def pack(v, io = nil, options = nil)
39
+ packer = DefaultFactory.packer(io, options)
41
40
  packer.write v
42
41
  packer.full_pack
43
42
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe MessagePack::Bigint do
4
+ it 'serialize and deserialize arbitrary sized integer' do
5
+ [
6
+ 1,
7
+ -1,
8
+ 120938120391283122132313,
9
+ -21903120391203912391023920332103,
10
+ 210290021321301203912933021323,
11
+ ].each do |int|
12
+ expect(MessagePack::Bigint.from_msgpack_ext(MessagePack::Bigint.to_msgpack_ext(int))).to be == int
13
+ end
14
+ end
15
+
16
+ it 'has a stable format' do
17
+ {
18
+ 120938120391283122132313 => "\x00\x9F\xF4UY\x11\x92\x9A?\x00\x00\x19\x9C".b,
19
+ -21903120391203912391023920332103 => "\x01/\xB2\xBDG\xBD\xDE\xAA\xEBt\xCC\x8A\xC1\x00\x00\x01\x14".b,
20
+ 210290021321301203912933021323 => "\x00\xC4\xD8\x96\x8Bm\xCB\xC7\x03\xA7{\xD4\"\x00\x00\x00\x02".b,
21
+ }.each do |int, payload|
22
+ expect(MessagePack::Bigint.to_msgpack_ext(int)).to be == payload
23
+ expect(MessagePack::Bigint.from_msgpack_ext(payload)).to be == int
24
+ end
25
+ end
26
+ end
data/spec/factory_spec.rb CHANGED
@@ -283,6 +283,147 @@ describe MessagePack::Factory do
283
283
  it { is_expected.to eq "unpacked module" }
284
284
  end
285
285
  end
286
+
287
+ describe "registering an ext type for Integer" do
288
+ let(:factory) { described_class.new }
289
+ let(:bigint) { 10**150 }
290
+
291
+ it 'does not work by default without passing `oversized_integer_extension: true`' do
292
+ factory.register_type(0x01, Integer, packer: :to_s, unpacker: method(:Integer))
293
+
294
+ expect do
295
+ factory.dump(bigint)
296
+ end.to raise_error RangeError
297
+ end
298
+
299
+ it 'raises ArgumentError if the type is not Integer' do
300
+ expect do
301
+ factory.register_type(0x01, MyType, packer: :to_s, unpacker: method(:Integer), oversized_integer_extension: true)
302
+ end.to raise_error(ArgumentError)
303
+ end
304
+
305
+ it 'invokes the packer if registered with `oversized_integer_extension: true`' do
306
+ factory.register_type(0x01, Integer, packer: :to_s, unpacker: method(:Integer), oversized_integer_extension: true)
307
+
308
+ expect(factory.load(factory.dump(bigint))).to be == bigint
309
+ end
310
+
311
+ it 'does not use the oversized_integer_extension packer for integers fitting in native types' do
312
+ factory.register_type(
313
+ 0x01,
314
+ Integer,
315
+ packer: ->(int) { raise NotImplementedError },
316
+ unpacker: ->(payload) { raise NotImplementedError },
317
+ oversized_integer_extension: true
318
+ )
319
+
320
+ expect(factory.dump(42)).to eq(MessagePack.dump(42))
321
+ end
322
+ end
323
+
324
+ describe "registering ext type with recursive serialization" do
325
+ before do
326
+ stub_const("Point", Struct.new(:x, :y, :z))
327
+ end
328
+
329
+ it 'can receive the packer as argument (proc)' do
330
+ factory = MessagePack::Factory.new
331
+ factory.register_type(0x00, Symbol)
332
+ factory.register_type(
333
+ 0x01,
334
+ Point,
335
+ packer: ->(point, packer) do
336
+ packer.write(point.to_h)
337
+ nil
338
+ end,
339
+ unpacker: ->(unpacker) do
340
+ attrs = unpacker.read
341
+ Point.new(attrs.fetch(:x), attrs.fetch(:y), attrs.fetch(:z))
342
+ end,
343
+ recursive: true,
344
+ )
345
+
346
+ point = Point.new(1, 2, 3)
347
+ payload = factory.dump(point)
348
+ expect(factory.load(payload)).to be == point
349
+ end
350
+
351
+ it 'can receive the packer as argument (Method)' do
352
+ mod = Module.new
353
+ mod.define_singleton_method(:packer) do |point, packer|
354
+ packer.write(point.to_h)
355
+ nil
356
+ end
357
+
358
+ mod.define_singleton_method(:unpacker) do |unpacker|
359
+ attrs = unpacker.read
360
+ Point.new(attrs.fetch(:x), attrs.fetch(:y), attrs.fetch(:z))
361
+ end
362
+
363
+ factory = MessagePack::Factory.new
364
+ factory.register_type(0x00, Symbol)
365
+ factory.register_type(
366
+ 0x01,
367
+ Point,
368
+ packer: mod.method(:packer),
369
+ unpacker: mod.method(:unpacker),
370
+ recursive: true,
371
+ )
372
+
373
+ point = Point.new(1, 2, 3)
374
+ payload = factory.dump(point)
375
+ expect(factory.load(payload)).to be == point
376
+ end
377
+
378
+ it 'respect message pack format' do
379
+ factory = MessagePack::Factory.new
380
+ factory.register_type(0x00, Symbol)
381
+ factory.register_type(
382
+ 0x01,
383
+ Point,
384
+ packer: ->(point, packer) do
385
+ packer.write(point.to_a)
386
+ nil
387
+ end,
388
+ unpacker: ->(unpacker) do
389
+ attrs = unpacker.read
390
+ Point.new(*attrs)
391
+ end,
392
+ recursive: true,
393
+ )
394
+
395
+ point = Point.new(1, 2, 3)
396
+ expect(factory.dump(point)).to be == "\xD6\x01".b + MessagePack.dump([1, 2, 3])
397
+ end
398
+
399
+ it 'sets the correct length' do
400
+ factory = MessagePack::Factory.new
401
+ factory.register_type(0x00, Symbol)
402
+ factory.register_type(
403
+ 0x01,
404
+ Point,
405
+ packer: ->(point, packer) do
406
+ packer.write(point.to_h)
407
+ nil
408
+ end,
409
+ unpacker: ->(unpacker) do
410
+ attrs = unpacker.read
411
+ Point.new(attrs.fetch(:x), attrs.fetch(:y), attrs.fetch(:z))
412
+ end,
413
+ recursive: true,
414
+ )
415
+
416
+ point = Point.new(1, 2, 3)
417
+ payload = factory.dump([1, point, 3])
418
+
419
+ obj = MessagePack::Factory.new.load(payload, allow_unknown_ext: true)
420
+ expect(obj).to be == [
421
+ 1,
422
+ MessagePack::ExtensionValue.new(1, factory.dump(x: 1, y: 2, z: 3)),
423
+ 3,
424
+ ]
425
+ end
426
+ end
286
427
  end
287
428
 
288
429
  describe 'the special treatment of symbols with ext type' do
@@ -437,4 +578,56 @@ describe MessagePack::Factory do
437
578
  expect(MessagePack.unpack(MessagePack.pack(dm2))).to eq(dm2)
438
579
  end
439
580
  end
581
+
582
+ describe '#pool' do
583
+ let(:factory) { described_class.new }
584
+
585
+ it 'responds to serializers interface' do
586
+ pool = factory.pool(1)
587
+ expect(pool.load(pool.dump(42))).to be == 42
588
+ end
589
+
590
+ it 'types can be registered before the pool is created' do
591
+ factory.register_type(0x00, Symbol)
592
+ pool = factory.pool(1)
593
+ expect(pool.load(pool.dump(:foo))).to be == :foo
594
+ end
595
+
596
+ it 'types cannot be registered after the pool is created' do
597
+ pool = factory.pool(1)
598
+ factory.register_type(0x20, ::MyType)
599
+
600
+ expect do
601
+ pool.dump(MyType.new(1, 2))
602
+ end.to raise_error NoMethodError
603
+
604
+ payload = factory.dump(MyType.new(1, 2))
605
+ expect do
606
+ pool.load(payload)
607
+ end.to raise_error MessagePack::UnknownExtTypeError
608
+ end
609
+
610
+ it 'support symbolize_keys: true' do
611
+ pool = factory.pool(1, symbolize_keys: true)
612
+ expect(pool.load(pool.dump('foo' => 1))).to be == { foo: 1 }
613
+ end
614
+
615
+ it 'support freeze: true' do
616
+ pool = factory.pool(1, freeze: true)
617
+ expect(pool.load(pool.dump('foo'))).to be_frozen
618
+ end
619
+
620
+ it 'is thread safe' do
621
+ pool = factory.pool(1)
622
+
623
+ threads = 10.times.map do
624
+ Thread.new do
625
+ 1_000.times do |i|
626
+ expect(pool.load(pool.dump(i))).to be == i
627
+ end
628
+ end
629
+ end
630
+ threads.each(&:join)
631
+ end
632
+ end
440
633
  end
data/spec/spec_helper.rb CHANGED
@@ -14,6 +14,7 @@ if ENV['GC_STRESS']
14
14
  end
15
15
 
16
16
  require 'msgpack'
17
+ require "msgpack/bigint"
17
18
 
18
19
  if GC.respond_to?(:verify_compaction_references)
19
20
  # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: msgpack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.5
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sadayuki Furuhashi
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-02-15 00:00:00.000000000 Z
13
+ date: 2022-04-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -176,6 +176,7 @@ files:
176
176
  - ext/msgpack/unpacker_ext_registry.c
177
177
  - ext/msgpack/unpacker_ext_registry.h
178
178
  - lib/msgpack.rb
179
+ - lib/msgpack/bigint.rb
179
180
  - lib/msgpack/core_ext.rb
180
181
  - lib/msgpack/factory.rb
181
182
  - lib/msgpack/packer.rb
@@ -186,6 +187,7 @@ files:
186
187
  - lib/msgpack/version.rb
187
188
  - msgpack.gemspec
188
189
  - msgpack.org.md
190
+ - spec/bigint_spec.rb
189
191
  - spec/cases.json
190
192
  - spec/cases.msg
191
193
  - spec/cases_compact.msg