Uploaded image for project: 'Java Driver'
  1. Java Driver
  2. JAVA-3364

PojoCodecImpl specializedPojoCodec race condition causes NPE

    • Type: Icon: Bug Bug
    • Resolution: Cannot Reproduce
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: 3.10.2
    • Component/s: POJO
    • Labels:
      None

      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.

            Assignee:
            john.stewart@mongodb.com John Stewart (Inactive)
            Reporter:
            kieron.edwards@me.com Kieron Edwards
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: