Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MappedFieldsTracker throws Exceptions #821

Open
xiaoxiaoxing opened this issue Apr 20, 2021 · 2 comments
Open

MappedFieldsTracker throws Exceptions #821

xiaoxiaoxing opened this issue Apr 20, 2021 · 2 comments
Labels

Comments

@xiaoxiaoxing
Copy link

xiaoxiaoxing commented Apr 20, 2021

Are you in the right place?

  • For issues or feature requests related to the code in this repository file a Github issue.
  • For general technical questions, post a question on Google Groups.

Whats your runtime?

  • Dozer version: 6.5.1
  • OS version: _____
  • JDK version: 8

Whats the problem?

I am trying to upgrade DozerMapper from5.4 to 6.x, I started to see spike of Exceptions from DozerMapper when deployed the change to production environment. I could not reproduce the issue in our gamma environment by manually triggering some requests. I am not familiar with the DozerMapper implemention, I am wondering do you have any idea what could happen here? Thanks.

There are two types of Exceptions:

  1. java.lang.IllegalStateException: No transaction with ID 0
  2. java.lang.NullPointerException: null

Looks like the "No transaction with ID" exception was thrown from pendingTransactions in MappedFieldsTracker, is MappedFieldsTracker a singleton? Is it supposed to be thread safe?

I pasted parts of the logs here:
java.lang.IllegalStateException: No transaction with ID 0 at com.github.dozermapper.core.MappedFieldsTracker.commitTransaction(MappedFieldsTracker.java:99) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.addOrUpdateToList(MappingProcessor.java:901) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.addOrUpdateToList(MappingProcessor.java:961) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapListToList(MappingProcessor.java:791) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapCollection(MappingProcessor.java:641) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:500) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapFromFieldMap(MappingProcessor.java:404) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapField(MappingProcessor.java:354) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:314) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapToDestObject(MappingProcessor.java:263) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.createByCreationDirectiveAndMap(MappingProcessor.java:236) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapGeneral(MappingProcessor.java:209) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:132) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:127) ~[dozer-core-6.5.1.jar:?] at xxxCustomConverter.convert(xxxCustomConverter.java:40) ~[xxxService-1.0.jar:?]

java.lang.NullPointerException: null at com.github.dozermapper.core.MappedFieldsTracker.put(MappedFieldsTracker.java:155) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:273) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapToDestObject(MappingProcessor.java:263) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.createByCreationDirectiveAndMap(MappingProcessor.java:236) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapCustomObject(MappingProcessor.java:575) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:512) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.addOrUpdateToList(MappingProcessor.java:885) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.addOrUpdateToList(MappingProcessor.java:961) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapListToList(MappingProcessor.java:791) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapCollection(MappingProcessor.java:641) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:500) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapFromFieldMap(MappingProcessor.java:404) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapField(MappingProcessor.java:354) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:314) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapToDestObject(MappingProcessor.java:263) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.createByCreationDirectiveAndMap(MappingProcessor.java:236) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.mapGeneral(MappingProcessor.java:209) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:132) ~[dozer-core-6.5.1.jar:?] at com.github.dozermapper.core.MappingProcessor.map(MappingProcessor.java:127) ~[dozer-core-6.5.1.jar:?] at xxxCustomConverter.convert(xxxCustomConverter.java:40) ~[xxxService-1.0.jar:?]

Observed Results:

Exceptions from DozerMapper.

Expected Results:

No Exception from DozerMapper.

Link to GitHub repo with Unit test

NA

@garethahealy
Copy link
Collaborator

@xiaoxiaoxing ; no, sorry. but looking at the stack, this is the only PR affecting MappedFieldsTracker

which was released in 6.5.0:

can you try 6.4.1?

@xiaoxiaoxing
Copy link
Author

Hi, @garethahealy:
Thanks for you reply!

Yes, I agree with you, I believe the issue is caused by the new transaction feature introduced in 6.5.0, looks like in multi-threads environment, the state of pendingTransactions in MappedFieldsTracker could be messed up.

I am able to reproduce the issue by unit test, and when I downgrade to 6.4.0, the issue is gone.

Here is my unit test:

public class ClassA {
    private String field1;
    private Long field2;
    private Map<String, String> field3;
    private Map<String, ClassB> field4;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Long getField2() {
        return field2;
    }

    public void setField2(Long field2) {
        this.field2 = field2;
    }

    public Map<String, String> getField3() {
        return field3;
    }

    public void setField3(Map<String, String> field3) {
        this.field3 = field3;
    }

    public Map<String, ClassB> getField4() {
        return field4;
    }

    public void setField4(Map<String, ClassB> field4) {
        this.field4 = field4;
    }
}
public class ClassB {
    private String field1;
    private Long field2 ;
    private List<ClassC> field3;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Long getField2() {
        return field2;
    }

    public void setField2(Long field2) {
        this.field2 = field2;
    }

    public List<ClassC> getField3() {
        return field3;
    }

    public void setField3(final List<ClassC> field3) {
        this.field3 = field3;
    }
}
public class ClassC {
    private String field1;
    private Long field2;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Long getField2() {
        return field2;
    }

    public void setField2(Long field2) {
        this.field2 = field2;
    }
}
public class ClassAA {
    private String field1;
    private Long field2;
    private Map<String, String> field3;
    private List<ClassBB> field4;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Long getField2() {
        return field2;
    }

    public void setField2(Long field2) {
        this.field2 = field2;
    }

    public Map<String, String> getField3() {
        return field3;
    }

    public void setField3(Map<String, String> field3) {
        this.field3 = field3;
    }

    public List<ClassBB> getField4() {
        return field4;
    }

    public void setField4(List<ClassBB> field4) {
        this.field4 = field4;
    }
}
public class ClassBB {
    private String field1;
    private Long field2 ;
    private List<ClassCC> field3;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Long getField2() {
        return field2;
    }

    public void setField2(Long field2) {
        this.field2 = field2;
    }

    public List<ClassCC> getField3() {
        return field3;
    }

    public void setField3(final List<ClassCC> field3) {
        this.field3 = field3;
    }
}
public class ClassCC {
    private String field1;
    private Long field2;

    public String getField1() {
        return field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Long getField2() {
        return field2;
    }

    public void setField2(Long field2) {
        this.field2 = field2;
    }
}
public class MyCustomConverter implements CustomConverter, MapperAware {
    private Mapper mapper;

    @SuppressWarnings("unchecked")
    @Override
    @Nullable
    public Object convert(
        @Nullable Object destination,
        @Nullable Object source,
        @Nonnull Class<?> destClass,
        @Nonnull Class<?> sourceClass) {

        if (source == null) {
            return null;
        }

        if (source instanceof Map) {
            Map<String, ClassB> inputMap = (Map<String, ClassB>)source;
            List<ClassBB> convertedValues = new ArrayList<>();
            for (Map.Entry<String, ClassB> entry : inputMap.entrySet()) {
                ClassBB classBB = mapper.map(entry.getValue(), ClassBB.class);
                convertedValues.add(classBB);
            }

            return convertedValues;
        } else {
            throw new MappingException("Converter MarketplaceSubscriptionDetailsCustomConverter "
                + "used incorrectly. Arguments passed in were:"
                + destination + " and " + source);
        }
    }

    @Override
    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }
}
<bean id="myCustomConverter"
          class="com.amazon.prise.dao.dto.converters.MyCustomConverter"/>

<bean id="dozerBeanMapperBuilderWithMappingFilesAndCustomConvertersWithId" class="com.github.dozermapper.core.DozerBeanMapperBuilder" factory-bean="dozerBeanMapperBuilderWithMappingFiles" factory-method="withCustomConvertersWithIds">
        <constructor-arg name="customConvertersWithId">
            <map>
                <entry key="myCustomConverter" value-ref="myCustomConverter" />
            </map>
        </constructor-arg>
</bean>
<mapping type="one-way">
        <class-a>com.amazon.prise.activity.ClassA</class-a>
        <class-b>com.amazon.prise.activity.ClassAA</class-b>

        <field custom-converter-id="myCustomConverter">
            <a>field4</a>
            <b>field4</b>
        </field>
</mapping>
private ClassA createInput_concurrency(String str, long l) {
        ClassC classC = new ClassC();
        classC.setField1("Field1" + str);
        classC.setField2(123L + l);
        List<ClassC> classCList = new ArrayList<>();
        classCList.add(classC);

        ClassB classB = new ClassB();
        classB.setField1("Field1" + str);
        classB.setField2(123L + l);
        classB.setField3(classCList);
        Map<String, ClassB> classBMap = new HashMap<>();
        classBMap.put("Key" + str, classB);

        Map<String, String> map = new HashMap<>();
        map.put("K1" + str, "V1");
        map.put("K2" + str, "V2");
        map.put("K3" + str, "V3");

        ClassA classA = new ClassA();
        classA.setField1("Field1" + str);
        classA.setField2(123L + l);
        classA.setField3(map);
        classA.setField4(classBMap);

        return classA;
 }

@Test
public void convert_concurrency() {
        replayAll();

        int numOfThreads = 200;
        ExecutorService service = Executors.newFixedThreadPool(numOfThreads);
        for (int i = 0; i < numOfThreads; i++) {
            String string = String.valueOf(i);
            long l = i;
            service.execute(() -> {
                ClassA input = createInput_concurrency(string, l);
                Object output = dozerMapper.map(input, ClassAA.class);
            });
        }
 }

Run the above test for a couple times, you would see either IllegalStateException or NPE will be thrown from some of the threads in MappedFieldsTracker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants