[ 
https://issues.apache.org/jira/browse/IGNITE-20688?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17850685#comment-17850685
 ] 

Mikhail Petrov commented on IGNITE-20688:
-----------------------------------------

Root cause of the issue:
Ignite does not consider references (handles) during the Binary Object 
detachment process.
As a result, after detaching a binary object, it may contain references to the 
part of the binary array that was cut off during the detaching procedure.
This issue primarily affects Java thin client and cache operations that use 
collections (maps/arrays/lists, etc.) as the value.

Java thin client serializes collection as a single object and reuses references 
across all collections elements.
When a cache request with a serialized collection is received by the Ignite 
server node, it is unmarshalled to a collection of binary objects, each 
containing its own chunk of of the serialized value bytes after "detach" 
procedure. This is where the problem with orphaned references can arises.

The following approaches for solving this issue was considered:
1. The most preferred way is to completely avoid unmarshalling value bytes 
​​serialized by the thin client during cache#put operations (marshalling cache 
value bytes stored in cache during thin client cache#get operations) on  server 
nodes (see https://issues.apache.org/jira/browse/IGNITE-12625).  

Currently this approach is error prone and may cause more problems than it 
solves. Ignite intercepts values during cache operations processing - it can be 
JCache interceptors or different listeners/filters/policies. In this cases we 
are forced to unmarshal raw value bytes to Binary Object and we must avoid of 
detach operation as it can lead to the mentioned orphaned references problem. 
 Keep in mind that if the cache value is a collection of objects, then after 
unmarshalling we will end up with a collection of binary objects, each 
containing a reference to a byte array that contains ALL the elements of the 
serialized collections and corresponding offset.  If we will try to store these 
Binary Objects in cache or send them over the network, we may  end up with 
sending/storing byte array containing ALL serialized collection elements for 
each collection's element. And this situation is difficult to detect because of 
mentioned interceptors.

Here you should also consider that collections serialized on thin client may 
differ from the  ones serialized on Ignite node. Because thin client serializes 
collection as single element and Ignite node serialize each element 
independently. As a result it is possible that in some cases keys that are 
stored using thin client will not be visible from the nodes and vice versa or 
we can face problems with CAS operations.

2. Resolving orphaned references during Binary Object detach procedure - we 
just replace orphaned reference with object and reassemble the binary object.

This approach eliminates differences in collection serialization between Ignite 
node and Thin client. So mentioned problems with CAS or missing keys are no 
longer available. The goal is not to avoid the binary object detach process, 
but to make it work correctly. Also it is compatible with thin client of old 
versions.

But detection of orphaned references is costly - we need to iterate over object 
binary representation. Not to mention reassembling the object if we need to 
replace the reference to the real object.

The second approach was chosen. As a result we will get performance drop for 
cache operations that uses collections as value and are performed from java 
thin client. The degree of degradation depends on the structure of the 
collection's values ​​and the length of the collection. 
You can consider it to be around 4-5% in case cross object references are not 
found. Still it can be completely eliminated by wrapping collection to object.








> Java Thin Client - Error while deserializing Collection
> -------------------------------------------------------
>
>                 Key: IGNITE-20688
>                 URL: https://issues.apache.org/jira/browse/IGNITE-20688
>             Project: Ignite
>          Issue Type: Bug
>          Components: binary, thin client
>    Affects Versions: 2.9, 2.10, 2.12, 2.13, 2.14, 2.15
>            Reporter: Rahul Mohan
>            Assignee: Mikhail Petrov
>            Priority: Critical
>              Labels: ise
>         Attachments: image001.png
>
>          Time Spent: 1h 20m
>  Remaining Estimate: 0h
>
> I have encountered an issue in deserializing cache values which are of 
> Collection type.
> The issue occurs if a field in different objects  within the  collection 
> points  to the same reference.
> *Versions:*
> org.apache.ignite:ignite-core:2.9.0 to org.apache.ignite:ignite-core:2.15.0
>  
> {code:java}
> Person.java
> public class Person implements Serializable {
>     private String id;
>     private String firstName;
>     private String lastName;
>     private double salary;
>     private String country;
>     private String deleted;
>     private Set<String> accounts;
> }
> Client
>             ClientCacheConfiguration cacheCfg = new 
> ClientCacheConfiguration().setName(cacheName).
>                     setCacheMode(CacheMode.REPLICATED).
>                     
> setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
>  
>             cache = client.getOrCreateCache(cacheCfg);
>  
>             Set<String> set = new HashSet<>();
>             set.add("1");
>  
>             List<Person> persons = new ArrayList<>();
>             persons.add(new Person("105286a4","Jack","Smith",10000f, 
> "USA","false", set));
>             persons.add(new Person("98545b0fd3af","John", "Doe", 500000f, 
> "Australia","false", null));
>             persons.add(new Person("98545b0fd3afd","Hari","M",400000f, 
> "India", null, null));
>             persons.add(new 
> Person("985488b0fd3ae","Bugs","Bunny",300000f,"Wabbit Land ", null, set));
>             cache.put("group1", value) // Write collection to cache
>     
>             List<Person> persons = (List<Person>) cache.get("group1"); // Get 
> from cache, Exception here {code}
>     
> *Exception:*
> {code:java}
> class org.apache.ignite.binary.BinaryObjectException: Failed to deserialize 
> object [typeName=com.ignite.example.model.Person]
>                 at 
> org.apache.ignite.internal.binary.BinaryClassDescriptor.read(BinaryClassDescriptor.java:927)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.deserialize0(BinaryReaderExImpl.java:1764)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.deserialize(BinaryReaderExImpl.java:1716)
>                 at 
> org.apache.ignite.internal.binary.GridBinaryMarshaller.deserialize(GridBinaryMarshaller.java:316)
>                 at 
> org.apache.ignite.internal.client.thin.ClientBinaryMarshaller.deserialize(ClientBinaryMarshaller.java:74)
>                 at 
> org.apache.ignite.internal.client.thin.ClientUtils.unwrapBinary(ClientUtils.java:557)
>                 at 
> org.apache.ignite.internal.client.thin.ClientUtils.unwrapCollection(ClientUtils.java:578)
>                 at 
> org.apache.ignite.internal.client.thin.ClientUtils.unwrapBinary(ClientUtils.java:562)
>                 at 
> org.apache.ignite.internal.client.thin.ClientUtils.readObject(ClientUtils.java:546)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientCache.readObject(TcpClientCache.java:556)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientCache.readObject(TcpClientCache.java:561)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientCache$$Lambda$395/1950117092.apply(Unknown
>  Source)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientChannel.receive(TcpClientChannel.java:284)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientChannel.service(TcpClientChannel.java:219)
>                 at 
> org.apache.ignite.internal.client.thin.ReliableChannel.service(ReliableChannel.java:198)
>                 at 
> org.apache.ignite.internal.client.thin.ReliableChannel.affinityService(ReliableChannel.java:261)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientCache.cacheSingleKeyOperation(TcpClientCache.java:508)
>                 at 
> org.apache.ignite.internal.client.thin.TcpClientCache.get(TcpClientCache.java:111)
>                 at 
> com.ignite.example.service.ApacheIgniteService.printAllKeys(ApacheIgniteService.java:117)
>                 at 
> com.ignite.example.service.ApacheIgniteService.init(ApacheIgniteService.java:103)
>                 at 
> com.ignite.example.IgniteCacheExampleApplication.run(IgniteCacheExampleApplication.java:22)
>                 at 
> org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:768)
>                 at 
> org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:752)
>                 at 
> org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
>                 at 
> com.ignite.example.IgniteCacheExampleApplication.main(IgniteCacheExampleApplication.java:17)
> Caused by: class org.apache.ignite.binary.BinaryObjectException: Failed to 
> read field [name=accounts]
>                 at 
> org.apache.ignite.internal.binary.BinaryFieldAccessor.read(BinaryFieldAccessor.java:192)
>                 at 
> org.apache.ignite.internal.binary.BinaryClassDescriptor.read(BinaryClassDescriptor.java:888)
>                 ... 24 more
> Caused by: java.lang.ArrayIndexOutOfBoundsException: -430
>                 at 
> org.apache.ignite.internal.binary.streams.BinaryHeapInputStream.readByteAndShift(BinaryHeapInputStream.java:120)
>                 at 
> org.apache.ignite.internal.binary.streams.BinaryAbstractInputStream.readByte(BinaryAbstractInputStream.java:37)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.<init>(BinaryReaderExImpl.java:219)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.<init>(BinaryReaderExImpl.java:186)
>                 at 
> org.apache.ignite.internal.binary.BinaryUtils.doReadObject(BinaryUtils.java:1801)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.deserialize0(BinaryReaderExImpl.java:1748)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.deserialize(BinaryReaderExImpl.java:1716)
>                 at 
> org.apache.ignite.internal.binary.BinaryReaderExImpl.readField(BinaryReaderExImpl.java:1978)
>                 at 
> org.apache.ignite.internal.binary.BinaryFieldAccessor$DefaultFinalClassAccessor.read0(BinaryFieldAccessor.java:703)
>                 at 
> org.apache.ignite.internal.binary.BinaryFieldAccessor.read(BinaryFieldAccessor.java:188)
>  {code}
> *Analysis*
> The exception occurs due to a OutOfBounds read happening in
> obj = BinaryUtils.doReadObject(in, ctx, ldr, this);  
> This is because *handlePos* points to a location in the ByteBuffer which is 
> outside that of the Buffer in the current object resulting in a negative 
> offset (java.lang.ArrayIndexOutOfBoundsException: {*}-430{*})
> {{!image001.png!}}
> The serialization works and the  Byte array response returned from the server 
> is the same as that was written. It's the way HANDLE types within a 
> Collection are read  that's probably causing this.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to