msgpack 1.4.5 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +5 -5
  3. data/ChangeLog +32 -0
  4. data/README.md +25 -1
  5. data/bench/bench.rb +78 -0
  6. data/doclib/msgpack/factory.rb +45 -2
  7. data/ext/java/org/msgpack/jruby/Buffer.java +6 -0
  8. data/ext/java/org/msgpack/jruby/Decoder.java +23 -19
  9. data/ext/java/org/msgpack/jruby/Encoder.java +46 -18
  10. data/ext/java/org/msgpack/jruby/ExtensionRegistry.java +24 -31
  11. data/ext/java/org/msgpack/jruby/Factory.java +40 -5
  12. data/ext/java/org/msgpack/jruby/Packer.java +21 -11
  13. data/ext/java/org/msgpack/jruby/Unpacker.java +44 -22
  14. data/ext/msgpack/buffer.c +9 -36
  15. data/ext/msgpack/buffer.h +9 -1
  16. data/ext/msgpack/buffer_class.c +18 -9
  17. data/ext/msgpack/compat.h +0 -99
  18. data/ext/msgpack/extconf.rb +9 -11
  19. data/ext/msgpack/factory_class.c +62 -5
  20. data/ext/msgpack/packer.c +42 -29
  21. data/ext/msgpack/packer.h +25 -7
  22. data/ext/msgpack/packer_class.c +23 -20
  23. data/ext/msgpack/packer_ext_registry.c +13 -4
  24. data/ext/msgpack/packer_ext_registry.h +10 -5
  25. data/ext/msgpack/unpacker.c +99 -68
  26. data/ext/msgpack/unpacker.h +10 -6
  27. data/ext/msgpack/unpacker_class.c +16 -8
  28. data/ext/msgpack/unpacker_ext_registry.c +3 -2
  29. data/ext/msgpack/unpacker_ext_registry.h +5 -2
  30. data/lib/msgpack/bigint.rb +69 -0
  31. data/lib/msgpack/factory.rb +103 -0
  32. data/lib/msgpack/version.rb +1 -1
  33. data/lib/msgpack.rb +4 -5
  34. data/msgpack.gemspec +1 -0
  35. data/spec/bigint_spec.rb +26 -0
  36. data/spec/factory_spec.rb +248 -0
  37. data/spec/spec_helper.rb +9 -1
  38. data/spec/timestamp_spec.rb +2 -2
  39. data/spec/unpacker_spec.rb +12 -0
  40. metadata +20 -13
  41. data/bench/pack.rb +0 -23
  42. data/bench/pack_log.rb +0 -33
  43. data/bench/pack_log_long.rb +0 -65
  44. data/bench/pack_symbols.rb +0 -28
  45. data/bench/run.sh +0 -14
  46. data/bench/run_long.sh +0 -35
  47. data/bench/run_symbols.sh +0 -26
  48. data/bench/unpack.rb +0 -21
  49. data/bench/unpack_log.rb +0 -34
  50. data/bench/unpack_log_long.rb +0 -67
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a51f64e1c017cafcef6fe080992135f5c610984ecd126f4a3a2127baa4e4a644
4
- data.tar.gz: bf45726e8f6cb658390199ecb51345e034c908207b090d53934f037555d7c37a
3
+ metadata.gz: 9c3bd9f87eaddd3212797209da93c4ec3a1c2384cb4a41e0ea9feb48a75523f2
4
+ data.tar.gz: 16d6223c1c98f50e629c029f077383cbbd7cd194af038255fd28d37aeecbb39a
5
5
  SHA512:
6
- metadata.gz: 0ee24ab0cb11c648e9a022f23053c118c020b7c79cac14e00a1bb82faf90c59302727a2dc691c63a959225b06a5c40ec5e09fd8f35154c7474b1e652e27b9bc5
7
- data.tar.gz: 2fee550949875cc119f1865b9519c75794124ec17065b1da1ec0fd06af29ed7fbdd37d709bf8cdb217d3cf86e3004a18fab16a6ac032eec176735725439ec268
6
+ metadata.gz: 6d4bd9048f852ac4c6e1365b07e6a8df9869cd0d9b2ce2c8bff7c836fe3e513451cf8895b36a719d09beaafdb85101363b705da719e5e9e9c7361ee1de96f2cf
7
+ data.tar.gz: a15ac8e77af9dc0ccb06ed266efe1ff93a2bae78e2ab5b980ba87af486d08e34676f9203fbc6b646b3c7f14e2fbdf7d4e9e1892fd58d0ca214a39de0dd2ee345
@@ -25,12 +25,12 @@ jobs:
25
25
  bundler-cache: true # 'bundle install' and cache
26
26
  - run: bundle exec rake
27
27
 
28
- jruby:
28
+ other:
29
29
  strategy:
30
30
  fail-fast: false
31
31
  matrix:
32
32
  os: [ubuntu]
33
- ruby: ['jruby-9.2.19.0', 'jruby-9.3.3.0']
33
+ ruby: ['jruby-9.2.19.0', 'jruby-9.3.3.0', 'truffleruby']
34
34
  runs-on: ${{ matrix.os }}-latest
35
35
  steps:
36
36
  - uses: actions/checkout@v2
@@ -38,7 +38,7 @@ jobs:
38
38
  with:
39
39
  ruby-version: ${{ matrix.ruby }}
40
40
  bundler-cache: true # 'bundle install' and cache
41
- - run: bundle exec rake
41
+ - run: bundle exec rake spec
42
42
 
43
43
  head-versions:
44
44
  continue-on-error: true
@@ -46,7 +46,7 @@ jobs:
46
46
  fail-fast: false
47
47
  matrix:
48
48
  os: [ubuntu]
49
- ruby: ['ruby-head', 'jruby-head', 'truffleruby']
49
+ ruby: ['ruby-head', 'jruby-head']
50
50
  runs-on: ${{ matrix.os }}-latest
51
51
  steps:
52
52
  - uses: actions/checkout@v2
@@ -54,4 +54,4 @@ jobs:
54
54
  with:
55
55
  ruby-version: ${{ matrix.ruby }}
56
56
  bundler-cache: true # 'bundle install' and cache
57
- - run: bundle exec rake || echo "failed, but ignore it"
57
+ - run: bundle exec rake spec || echo "failed, but ignore it"
data/ChangeLog CHANGED
@@ -1,3 +1,35 @@
1
+ 2022-08-23 1.5.6:
2
+
3
+ * No actual code change, just re-release the `java` version properly.
4
+
5
+ 2022-08-22 1.5.5:
6
+
7
+ * Fix a segfault when GC triggers inside a recursive extension.
8
+
9
+ 2022-07-25 1.5.4:
10
+
11
+ * Fix a segfault when deserializing empty symbol (`:""`).
12
+ * Improve compilation flags to not strip debug symbols.
13
+
14
+ 2022-05-30 version 1.5.3:
15
+
16
+ * Fix deduplication of empty strings when using the `freeze: true` option.
17
+ * Use `rb_hash_new_capa` when available (Ruby 3.2) for improved performance when parsing large hashes.
18
+
19
+ 2022-05-27 version 1.5.2:
20
+
21
+ * Fix bug about unpacking ext type objects with the recursive option
22
+
23
+ 2022-04-07 version 1.5.1:
24
+
25
+ * Fix bug about packing/unpacking ext type objects with the recursive option
26
+
27
+ 2022-04-06 version 1.5.0:
28
+
29
+ * Add recursive option on Factory#register_type to operate Packer/Unpacker manually
30
+ * Add oversized_integer_extension option on Factory#register_type to pack/unpack bigint using ext types
31
+ * Add Factory#pool method and Factory::Pool class to provide pooled Packer and Unpacker instances
32
+
1
33
  2022-02-15 version 1.4.5:
2
34
 
3
35
  * Fix to create UTF-8 Symbol keys when symbolize_keys: true
data/README.md CHANGED
@@ -40,7 +40,7 @@ or build msgpack-ruby and install:
40
40
  MessagePack for Ruby should run on x86, ARM, PowerPC, SPARC and other CPU architectures.
41
41
 
42
42
  And it works with MRI (CRuby) and Rubinius.
43
- Patches to improve portability is highly welcomed.
43
+ Patches to improve portability are highly welcomed.
44
44
 
45
45
 
46
46
  ## Serializing objects
@@ -51,6 +51,7 @@ Use `MessagePack.pack` or `to_msgpack`:
51
51
  require 'msgpack'
52
52
  msg = MessagePack.pack(obj) # or
53
53
  msg = obj.to_msgpack
54
+ File.binwrite('mydata.msgpack', msg)
54
55
  ```
55
56
 
56
57
  ### Streaming serialization
@@ -71,6 +72,7 @@ Use `MessagePack.unpack`:
71
72
 
72
73
  ```ruby
73
74
  require 'msgpack'
75
+ msg = File.binread('mydata.msgpack')
74
76
  obj = MessagePack.unpack(msg)
75
77
  ```
76
78
 
@@ -187,6 +189,28 @@ MessagePack::DefaultFactory.register_type(0x03, MyClass3)
187
189
  MessagePack.unpack(data_with_ext_typeid_03) #=> MyClass3 instance
188
190
  ```
189
191
 
192
+ Alternatively, extension types can call the packer or unpacker recursively to generate the extension data:
193
+
194
+ ```ruby
195
+ Point = Struct.new(:x, :y)
196
+ factory = MessagePack::Factory.new
197
+ factory.register_type(
198
+ 0x01,
199
+ Point,
200
+ packer: ->(point, packer) {
201
+ packer.write(point.x)
202
+ packer.write(point.y)
203
+ },
204
+ unpacker: ->(unpacker) {
205
+ x = unpacker.read
206
+ y = unpacker.read
207
+ Point.new(x, y)
208
+ },
209
+ recursive: true,
210
+ )
211
+ factory.load(factory.dump(Point.new(12, 34))) # => #<struct Point x=12, y=34>
212
+ ```
213
+
190
214
  ## Buffer API
191
215
 
192
216
  MessagePack for Ruby provides a buffer API so that you can read or write data by hand, not via Packer or Unpacker API.
data/bench/bench.rb ADDED
@@ -0,0 +1,78 @@
1
+ # % bundle install
2
+ # % bundle exec ruby bench/bench.rb
3
+
4
+ require 'msgpack'
5
+
6
+ require 'benchmark/ips'
7
+
8
+ object_plain = {
9
+ 'message' => '127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I ;Nav)"'
10
+ }
11
+
12
+ data_plain = MessagePack.pack(object_plain)
13
+
14
+ object_structured = {
15
+ 'remote_host' => '127.0.0.1',
16
+ 'remote_user' => '-',
17
+ 'date' => '10/Oct/2000:13:55:36 -0700',
18
+ 'request' => 'GET /apache_pb.gif HTTP/1.0',
19
+ 'method' => 'GET',
20
+ 'path' => '/apache_pb.gif',
21
+ 'protocol' => 'HTTP/1.0',
22
+ 'status' => 200,
23
+ 'bytes' => 2326,
24
+ 'referer' => 'http://www.example.com/start.html',
25
+ 'agent' => 'Mozilla/4.08 [en] (Win98; I ;Nav)',
26
+ }
27
+
28
+ data_structured = MessagePack.pack(object_structured)
29
+
30
+ class Extended
31
+ def to_msgpack_ext
32
+ MessagePack.pack({})
33
+ end
34
+
35
+ def self.from_msgpack_ext(data)
36
+ MessagePack.unpack(data)
37
+ Extended.new
38
+ end
39
+ end
40
+
41
+ object_extended = {
42
+ 'extended' => Extended.new
43
+ }
44
+
45
+ extended_packer = MessagePack::Packer.new
46
+ extended_packer.register_type(0x00, Extended, :to_msgpack_ext)
47
+ data_extended = extended_packer.pack(object_extended).to_s
48
+
49
+ Benchmark.ips do |x|
50
+ x.report('pack-plain') do
51
+ MessagePack.pack(object_plain)
52
+ end
53
+
54
+ x.report('pack-structured') do
55
+ MessagePack.pack(object_structured)
56
+ end
57
+
58
+ x.report('pack-extended') do
59
+ packer = MessagePack::Packer.new
60
+ packer.register_type(0x00, Extended, :to_msgpack_ext)
61
+ packer.pack(object_extended).to_s
62
+ end
63
+
64
+ x.report('unpack-plain') do
65
+ MessagePack.unpack(data_plain)
66
+ end
67
+
68
+ x.report('unpack-structured') do
69
+ MessagePack.unpack(data_structured)
70
+ end
71
+
72
+ x.report('unpack-extended') do
73
+ unpacker = MessagePack::Unpacker.new
74
+ unpacker.register_type(0x00, Extended, :from_msgpack_ext)
75
+ unpacker.feed data_extended
76
+ unpacker.read
77
+ end
78
+ end
@@ -31,7 +31,7 @@ module MessagePack
31
31
  #
32
32
  # See Packer#initialize for supported options.
33
33
  #
34
- def dump(obj, options={})
34
+ def dump(obj, options=nil)
35
35
  end
36
36
  alias pack dump
37
37
 
@@ -57,7 +57,7 @@ module MessagePack
57
57
  #
58
58
  # See Unpacker#initialize for supported options.
59
59
  #
60
- def load(data, options={})
60
+ def load(data, options=nil)
61
61
  end
62
62
  alias unpack load
63
63
 
@@ -76,6 +76,7 @@ module MessagePack
76
76
  # * *:packer* specify symbol or proc object for packer
77
77
  # * *:unpacker* specify symbol or proc object for unpacker
78
78
  # * *:optimized_symbols_parsing* specify true to use the optimized symbols parsing (not supported on JRuby now)
79
+ # * *recursive* specify true to receive the packer or unpacker as argument to generate the extension body manually.
79
80
  #
80
81
  def register_type(type, klass, options={})
81
82
  end
@@ -98,5 +99,47 @@ module MessagePack
98
99
  #
99
100
  def type_registered?(klass_or_type, selector=:both)
100
101
  end
102
+
103
+ #
104
+ # Creates a MessagePack::PooledFactory instance of the given size.
105
+ #
106
+ # PooledFactory keeps Packer and Unpacker instance in a pool for improved performance.
107
+ # Note that the size defines how many instances are kept in cache, not the maximum of instances
108
+ # that can be created. If the pool limit is reached, a new instance is created anyway.
109
+ #
110
+ # @param size [Fixnum] specify how many Packer and Unpacker to keep in cache (default 1)
111
+ # @param options [Hash] Combined options for Packer and Unpacker. See Packer#initialize and Unpacker#initialize
112
+ # for supported options.
113
+ def pool(size=1, **options)
114
+ end
115
+
116
+ class Pool
117
+ #
118
+ # Deserializes an object from the string or io and returns it.
119
+ #
120
+ # If there're not enough data to deserialize one object, this method raises EOFError.
121
+ # If data format is invalid, this method raises MessagePack::MalformedFormatError.
122
+ # If the object nests too deeply, this method raises MessagePack::StackError.
123
+ #
124
+ # @param data [String]
125
+ # @return [Object] deserialized object
126
+ #
127
+ # See Unpacker#initialize for supported options.
128
+ #
129
+ def load(data)
130
+ end
131
+
132
+ #
133
+ # Serialize the passed value
134
+ #
135
+ # If it could not serialize the object, it raises
136
+ # NoMethodError: undefined method `to_msgpack' for #<the_object>.
137
+ #
138
+ # @param obj [Object] object to serialize
139
+ # @return [String] serialized object
140
+ #
141
+ def dump(object)
142
+ end
143
+ end
101
144
  end
102
145
  end
@@ -224,4 +224,10 @@ public class Buffer extends RubyObject {
224
224
  public IRubyObject writeTo(ThreadContext ctx, IRubyObject io) {
225
225
  return io.callMethod(ctx, "write", readCommon(ctx, null, false));
226
226
  }
227
+
228
+ public ByteList getBytes() {
229
+ byte[] bytes = new byte[rawSize()];
230
+ buffer.get(bytes);
231
+ return new ByteList(bytes, binaryEncoding);
232
+ }
227
233
  }
@@ -14,6 +14,7 @@ import org.jruby.RubyBignum;
14
14
  import org.jruby.RubyString;
15
15
  import org.jruby.RubyArray;
16
16
  import org.jruby.RubyHash;
17
+ import org.jruby.RubyInteger;
17
18
  import org.jruby.exceptions.RaiseException;
18
19
  import org.jruby.runtime.builtin.IRubyObject;
19
20
  import org.jruby.util.ByteList;
@@ -35,7 +36,7 @@ public class Decoder implements Iterator<IRubyObject> {
35
36
  private final RubyClass unexpectedTypeErrorClass;
36
37
  private final RubyClass unknownExtTypeErrorClass;
37
38
 
38
- private ExtensionRegistry registry;
39
+ private Unpacker unpacker;
39
40
  private ByteBuffer buffer;
40
41
  private boolean symbolizeKeys;
41
42
  private boolean freeze;
@@ -45,29 +46,29 @@ public class Decoder implements Iterator<IRubyObject> {
45
46
  this(runtime, null, new byte[] {}, 0, 0, false, false, false);
46
47
  }
47
48
 
48
- public Decoder(Ruby runtime, ExtensionRegistry registry) {
49
- this(runtime, registry, new byte[] {}, 0, 0, false, false, false);
49
+ public Decoder(Ruby runtime, Unpacker unpacker) {
50
+ this(runtime, unpacker, new byte[] {}, 0, 0, false, false, false);
50
51
  }
51
52
 
52
53
  public Decoder(Ruby runtime, byte[] bytes) {
53
54
  this(runtime, null, bytes, 0, bytes.length, false, false, false);
54
55
  }
55
56
 
56
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes) {
57
- this(runtime, registry, bytes, 0, bytes.length, false, false, false);
57
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes) {
58
+ this(runtime, unpacker, bytes, 0, bytes.length, false, false, false);
58
59
  }
59
60
 
60
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
61
- this(runtime, registry, bytes, 0, bytes.length, symbolizeKeys, freeze, allowUnknownExt);
61
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
62
+ this(runtime, unpacker, bytes, 0, bytes.length, symbolizeKeys, freeze, allowUnknownExt);
62
63
  }
63
64
 
64
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length) {
65
- this(runtime, registry, bytes, offset, length, false, false, false);
65
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, int offset, int length) {
66
+ this(runtime, unpacker, bytes, offset, length, false, false, false);
66
67
  }
67
68
 
68
- public Decoder(Ruby runtime, ExtensionRegistry registry, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
69
+ public Decoder(Ruby runtime, Unpacker unpacker, byte[] bytes, int offset, int length, boolean symbolizeKeys, boolean freeze, boolean allowUnknownExt) {
69
70
  this.runtime = runtime;
70
- this.registry = registry;
71
+ this.unpacker = unpacker;
71
72
  this.symbolizeKeys = symbolizeKeys;
72
73
  this.freeze = freeze;
73
74
  this.allowUnknownExt = allowUnknownExt;
@@ -154,18 +155,21 @@ public class Decoder implements Iterator<IRubyObject> {
154
155
 
155
156
  private IRubyObject consumeExtension(int size) {
156
157
  int type = buffer.get();
157
- byte[] payload = readBytes(size);
158
-
159
- if (registry != null) {
160
- IRubyObject proc = registry.lookupUnpackerByTypeId(type);
161
- if (proc != null) {
162
- ByteList byteList = new ByteList(payload, runtime.getEncodingService().getAscii8bitEncoding());
163
- return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
158
+ if (unpacker != null) {
159
+ ExtensionRegistry.ExtensionEntry entry = unpacker.lookupExtensionByTypeId(type);
160
+ if (entry != null) {
161
+ IRubyObject proc = entry.getUnpackerProc();
162
+ if (entry.isRecursive()) {
163
+ return proc.callMethod(runtime.getCurrentContext(), "call", unpacker);
164
+ } else {
165
+ ByteList byteList = new ByteList(readBytes(size), runtime.getEncodingService().getAscii8bitEncoding());
166
+ return proc.callMethod(runtime.getCurrentContext(), "call", runtime.newString(byteList));
167
+ }
164
168
  }
165
169
  }
166
170
 
167
171
  if (this.allowUnknownExt) {
168
- return ExtensionValue.newExtensionValue(runtime, type, payload);
172
+ return ExtensionValue.newExtensionValue(runtime, type, readBytes(size));
169
173
  }
170
174
 
171
175
  throw runtime.newRaiseException(unknownExtTypeErrorClass, "unexpected extension type");
@@ -38,12 +38,16 @@ public class Encoder {
38
38
  private final Encoding utf8Encoding;
39
39
  private final boolean compatibilityMode;
40
40
  private final ExtensionRegistry registry;
41
+ private final Packer packer;
41
42
 
42
43
  public boolean hasSymbolExtType;
44
+ private boolean hasBigintExtType;
45
+ private boolean recursiveExtension;
43
46
 
44
47
  private ByteBuffer buffer;
45
48
 
46
- public Encoder(Ruby runtime, boolean compatibilityMode, ExtensionRegistry registry, boolean hasSymbolExtType) {
49
+ public Encoder(Ruby runtime, Packer packer, boolean compatibilityMode, ExtensionRegistry registry, boolean hasSymbolExtType, boolean hasBigintExtType) {
50
+ this.packer = packer;
47
51
  this.runtime = runtime;
48
52
  this.buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
49
53
  this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
@@ -51,6 +55,7 @@ public class Encoder {
51
55
  this.compatibilityMode = compatibilityMode;
52
56
  this.registry = registry;
53
57
  this.hasSymbolExtType = hasSymbolExtType;
58
+ this.hasBigintExtType = hasBigintExtType;
54
59
  }
55
60
 
56
61
  public boolean isCompatibilityMode() {
@@ -66,9 +71,17 @@ public class Encoder {
66
71
  }
67
72
 
68
73
  private IRubyObject readRubyString() {
69
- IRubyObject str = runtime.newString(new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false));
70
- buffer.clear();
71
- return str;
74
+ if (recursiveExtension) {
75
+ // If recursiveExtension is true, it means we re-entered encode, so we MUST NOT flush the buffer.
76
+ // Instead we return an empty string to act as a null object for the caller. The buffer will actually
77
+ // be flushed once we're done serializing the recursive extension.
78
+ // All other method that consume the buffer should do so through readRubyString or implement the same logic.
79
+ return runtime.newString();
80
+ } else {
81
+ IRubyObject str = runtime.newString(new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false));
82
+ buffer.clear();
83
+ return str;
84
+ }
72
85
  }
73
86
 
74
87
  public IRubyObject encode(IRubyObject object) {
@@ -147,7 +160,10 @@ public class Encoder {
147
160
  BigInteger value = object.getBigIntegerValue();
148
161
  if (value.compareTo(RubyBignum.LONG_MIN) < 0 || value.compareTo(RubyBignum.LONG_MAX) > 0) {
149
162
  if (value.bitLength() > 64 || (value.bitLength() > 63 && value.signum() < 0)) {
150
- throw runtime.newArgumentError(String.format("Cannot pack big integer: %s", value));
163
+ if (hasBigintExtType && tryAppendWithExtTypeLookup(object)) {
164
+ return;
165
+ }
166
+ throw runtime.newRangeError(String.format("Cannot pack big integer: %s", value));
151
167
  }
152
168
  ensureRemainingCapacity(9);
153
169
  buffer.put(value.signum() < 0 ? INT64 : UINT64);
@@ -391,19 +407,31 @@ public class Encoder {
391
407
 
392
408
  private boolean tryAppendWithExtTypeLookup(IRubyObject object) {
393
409
  if (registry != null) {
394
- RubyModule lookupClass;
395
-
396
- if (object.getType() == runtime.getSymbol()) {
397
- lookupClass = object.getType();
398
- } else {
399
- lookupClass = object.getSingletonClass();
400
- }
401
-
402
- IRubyObject[] pair = registry.lookupPackerForObject(object);
403
- if (pair != null) {
404
- RubyString bytes = pair[0].callMethod(runtime.getCurrentContext(), "call", object).asString();
405
- int type = (int) ((RubyFixnum) pair[1]).getLongValue();
406
- appendExt(type, bytes.getByteList());
410
+ ExtensionRegistry.ExtensionEntry entry = registry.lookupExtensionForObject(object);
411
+ if (entry != null) {
412
+ IRubyObject proc = entry.getPackerProc();
413
+ int type = entry.getTypeId();
414
+
415
+ if (entry.isRecursive()) {
416
+ ByteBuffer oldBuffer = buffer;
417
+ buffer = ByteBuffer.allocate(CACHE_LINE_SIZE - ARRAY_HEADER_SIZE);
418
+ boolean previousRecursiveExtension = recursiveExtension;
419
+ recursiveExtension = true;
420
+
421
+ ByteList payload;
422
+ try {
423
+ IRubyObject args[] = { object, packer };
424
+ proc.callMethod(runtime.getCurrentContext(), "call", args);
425
+ payload = new ByteList(buffer.array(), 0, buffer.position(), binaryEncoding, false);
426
+ } finally {
427
+ recursiveExtension = previousRecursiveExtension;
428
+ buffer = oldBuffer;
429
+ }
430
+ appendExt(type, payload);
431
+ } else {
432
+ RubyString bytes = proc.callMethod(runtime.getCurrentContext(), "call", object).asString();
433
+ appendExt(type, bytes.getByteList());
434
+ }
407
435
  return true;
408
436
  }
409
437
  }
@@ -59,59 +59,46 @@ public class ExtensionRegistry {
59
59
  return hash;
60
60
  }
61
61
 
62
- public void put(RubyModule mod, int typeId, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
63
- ExtensionEntry entry = new ExtensionEntry(mod, typeId, packerProc, packerArg, unpackerProc, unpackerArg);
62
+ public void put(RubyModule mod, int typeId, boolean recursive, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
63
+ ExtensionEntry entry = new ExtensionEntry(mod, typeId, recursive, packerProc, packerArg, unpackerProc, unpackerArg);
64
64
  extensionsByModule.put(mod, entry);
65
65
  extensionsByTypeId[typeId + 128] = entry;
66
66
  extensionsByAncestor.clear();
67
67
  }
68
68
 
69
- public IRubyObject lookupUnpackerByTypeId(int typeId) {
69
+ public ExtensionEntry lookupExtensionByTypeId(int typeId) {
70
70
  ExtensionEntry e = extensionsByTypeId[typeId + 128];
71
71
  if (e != null && e.hasUnpacker()) {
72
- return e.getUnpackerProc();
73
- } else {
74
- return null;
72
+ return e;
75
73
  }
74
+ return null;
76
75
  }
77
76
 
78
- public IRubyObject[] lookupPackerForObject(IRubyObject object) {
77
+ public ExtensionEntry lookupExtensionForObject(IRubyObject object) {
79
78
  RubyModule lookupClass = null;
80
- IRubyObject[] pair;
79
+ ExtensionEntry entry = null;
81
80
  /*
82
81
  * Objects of type Integer (Fixnum, Bignum), Float, Symbol and frozen
83
82
  * String have no singleton class and raise a TypeError when trying to get
84
83
  * it.
85
84
  */
86
85
  lookupClass = object.getMetaClass();
87
- pair = fetchEntryByModule(lookupClass);
88
- if (pair != null) {
89
- return pair;
86
+ entry = extensionsByModule.get(lookupClass);
87
+ if (entry != null && entry.hasPacker()) {
88
+ return entry;
90
89
  }
91
90
 
92
91
  RubyModule realClass = object.getType();
93
92
  if (realClass != lookupClass) {
94
- pair = fetchEntryByModule(realClass);
95
- if (pair != null) {
96
- return pair;
93
+ entry = extensionsByModule.get(realClass);
94
+ if (entry != null && entry.hasPacker()) {
95
+ return entry;
97
96
  }
98
97
  }
99
98
 
100
- ExtensionEntry e = findEntryByModuleOrAncestor(lookupClass);
101
- if (e != null && e.hasPacker()) {
102
- extensionsByAncestor.put(e.getExtensionModule(), e);
103
- return e.toPackerProcTypeIdPair(lookupClass.getRuntime().getCurrentContext());
104
- }
105
- return null;
106
- }
107
-
108
- private IRubyObject[] fetchEntryByModule(final RubyModule mod) {
109
- ExtensionEntry e = extensionsByModule.get(mod);
110
- if (e == null) {
111
- e = extensionsByAncestor.get(mod);
112
- }
113
- if (e != null && e.hasPacker()) {
114
- return e.toPackerProcTypeIdPair(mod.getRuntime().getCurrentContext());
99
+ entry = findEntryByModuleOrAncestor(lookupClass);
100
+ if (entry != null && entry.hasPacker()) {
101
+ return entry;
115
102
  }
116
103
  return null;
117
104
  }
@@ -127,17 +114,19 @@ public class ExtensionRegistry {
127
114
  return null;
128
115
  }
129
116
 
130
- private static class ExtensionEntry {
117
+ public static class ExtensionEntry {
131
118
  private final RubyModule mod;
132
119
  private final int typeId;
120
+ private final boolean recursive;
133
121
  private final IRubyObject packerProc;
134
122
  private final IRubyObject packerArg;
135
123
  private final IRubyObject unpackerProc;
136
124
  private final IRubyObject unpackerArg;
137
125
 
138
- public ExtensionEntry(RubyModule mod, int typeId, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
126
+ public ExtensionEntry(RubyModule mod, int typeId, boolean recursive, IRubyObject packerProc, IRubyObject packerArg, IRubyObject unpackerProc, IRubyObject unpackerArg) {
139
127
  this.mod = mod;
140
128
  this.typeId = typeId;
129
+ this.recursive = recursive;
141
130
  this.packerProc = packerProc;
142
131
  this.packerArg = packerArg;
143
132
  this.unpackerProc = unpackerProc;
@@ -152,6 +141,10 @@ public class ExtensionRegistry {
152
141
  return typeId;
153
142
  }
154
143
 
144
+ public boolean isRecursive() {
145
+ return recursive;
146
+ }
147
+
155
148
  public boolean hasPacker() {
156
149
  return packerProc != null;
157
150
  }