[JAVA-3364] PojoCodecImpl specializedPojoCodec race condition causes NPE Created: 26/Jul/19  Updated: 08/Oct/19  Resolved: 08/Oct/19

Status: Closed
Project: Java Driver
Component/s: POJO
Affects Version/s: 3.10.2
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Kieron Edwards Assignee: John Stewart (Inactive)
Resolution: Cannot Reproduce Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates JAVA-3456 ClassModel needs to be immutable Closed

 Description   

Problem

During a multi-threaded bulk write a NPE thrown due to null cachedCodec being set on the PropertyModel :-

 Caused by: java.lang.NullPointerException
 at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
 at org.bson.codecs.pojo.PojoCodecImpl.encodeValue(PojoCodecImpl.java:180)
 at org.bson.codecs.pojo.PojoCodecImpl.encodeProperty(PojoCodecImpl.java:168)
 at org.bson.codecs.pojo.PojoCodecImpl.encode(PojoCodecImpl.java:105)
 at org.bson.codecs.pojo.LazyPojoCodec.encode(LazyPojoCodec.java:47)
 at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
 at org.bson.codecs.pojo.PojoCodecImpl.encodeValue(PojoCodecImpl.java:180)
 at org.bson.codecs.pojo.PojoCodecImpl.encodeProperty(PojoCodecImpl.java:168)
 at org.bson.codecs.pojo.PojoCodecImpl.encode(PojoCodecImpl.java:105)
 at org.bson.codecs.pojo.LazyPojoCodec.encode(LazyPojoCodec.java:47)
 at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
 at org.bson.codecs.pojo.PojoCodecImpl.encodeValue(PojoCodecImpl.java:180) 

Cause

In PojoCodecImpl a race condition may occur in the specializedPojoCodec method between the containsKey and get call against the ConcurrentHashMap :-

            ClassModel<S> specialized = getSpecializedClassModel(pojoCodec.getClassModel(), propertyModel);
            if (codecCache.containsKey(specialized)) {
                codec = (Codec<S>) codecCache.get(specialized);
            } else {
                codec = new LazyPojoCodec<S>(specialized, registry, propertyCodecRegistry, discriminatorLookup, codecCache);
            }
        }
        return codec;
    }

In this case the specialized keys ClassModel hashCode changes between the two calls resulting in a cache miss and the codec getting set to null

Solution

Replace the containsKey and get calls with a thread safe call to computeIfAbsent :-

            ClassModel<S> specialized = getSpecializedClassModel(pojoCodec.getClassModel(), propertyModel); 
            codec = (Codec<S>) codecCache.computeIfAbsent( specialized, x-> new LazyPojoCodec<>((ClassModel<S>) x, registry, propertyCodecRegistry, discriminatorLookup, codecCache));

Further Suggestions

It would suggest the change to hashCode indicates ClassModel is not immutable. Its collections are directly exposed via getters and can be changed after construction. Can a copy of the collections be returned by the getters instead.



 Comments   
Comment by John Stewart (Inactive) [ 08/Oct/19 ]

We were unable to reproduce the issue. If an example POJO is provided that reproduces the issue, we will reopen the investigation. The ticket JAVA-3456 has been opened to make `ClassModel` immutable.

Comment by John Stewart (Inactive) [ 11/Sep/19 ]

kieron.edwards@me.com We would like to fully understand the issue causing this bug, and providing the POJO you used to generate this issue would help us greatly. Any information you can provide would be appreciated. Cheers!

Comment by John Stewart (Inactive) [ 12/Aug/19 ]

kieron.edwards@me.com Thanks for reporting this. While investigating this ticket, I found that I do not have a full grasp of the problem, and therefore my attempt at a fix may not cover this ticket in its entirety. Would you be able to share the POJO that you are using? It would help me better understand the issue and create a reproducible test case. Thanks.

Comment by John Stewart (Inactive) [ 06/Aug/19 ]

The map and list fields in `ClassModel` need to be initialized with defensive copies and then be made unmodifiable. Reopening the ticket to address this.

Comment by Githook User [ 06/Aug/19 ]

Author:

{'name': 'John Stewart', 'username': 'jstewart-mongo', 'email': 'john.stewart@mongodb.com'}

Message: Make ClassModel immutable

JAVA-3364
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/b1ce857668c36d560eb6a8ce2ac72e993677eec6

Generated at Thu Feb 08 08:59:26 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.