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

Fix Enum deserialization with JsonFormat.Shape.OBJECT using both DELEGATING and PROPERTIES creator modes #3851

Merged
merged 6 commits into from
Apr 1, 2023

Conversation

JooHyukKim
Copy link
Member

@JooHyukKim JooHyukKim commented Mar 29, 2023

resolves #3566

}
p.nextToken();
return deserializeEnumUsingPropertyBased(p, ctxt, _propCreator);
}
Copy link
Member Author

@JooHyukKim JooHyukKim Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary here,

  1. Simply flipped the p.isExpectedStartObjectToken() conditional (line 135)
  2. Allow pass-through of case for possible "delegating-creator" (line 144) -- in other words, "fail if f value cannot possibly be delegating-creator"

@@ -132,18 +132,21 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
// 30-Mar-2020, tatu: For properties-based one, MUST get JSON Object (before
// 2.11, was just assuming match)
if (_creatorProps != null) {
if (!p.isExpectedStartObjectToken()) {
if (p.isExpectedStartObjectToken()) {
if (_propCreator == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I know this is existing code but this is wrong (not sure how I added such code :) ), wrt multi-threaded access.
Could you extract this into separate, synchronized method? At least it'd be slightly less bad.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure! no problem

Copy link
Member Author

@JooHyukKim JooHyukKim Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

synchronize assignment to _propCreator right, @cowtowncoder ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so it's transient but that won't do anything for access. I think what'd make sense is using AtomicReference here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be thread-safe, now?

@@ -132,18 +135,21 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
// 30-Mar-2020, tatu: For properties-based one, MUST get JSON Object (before
// 2.11, was just assuming match)
if (_creatorProps != null) {
if (!p.isExpectedStartObjectToken()) {
if (p.isExpectedStartObjectToken()) {
_propCreatorRef.compareAndSet(null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But wouldn't this create (and try set) a new instance every time? :-D
(instead of trying to get() and only create-and-set if null)

Copy link
Member Author

@JooHyukKim JooHyukKim Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this create (and try set) a new instance every time? :-D
hmm, Not really my intention 😅

Could you check again? Take below as an example. Invoking get() and set separately wouldn't be atomic anymore 🤔.

    public void testAtomicRef() {
        Bean expected = new Bean(0);

        AtomicReference<Bean> reference = new AtomicReference<>(null);
        reference.compareAndSet(null, expected);
        reference.compareAndSet(null, new Bean(1));
        reference.compareAndSet(null, new Bean(2));

       // Passes
        assertEquals(0,reference.get().a);
    }

According to compareAndSet documentation,

Atomically sets the value to newValue if the current value == expectedValue, with memory effects

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or we can do good-ol' double-checked locking like ones done in EnumDeserializer below.

protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt) {
CompactStringObjectMap lookup = _lookupByToString;
if (lookup == null) {
synchronized (this) {
lookup = _lookupByToString;
if (lookup == null) {
lookup = EnumResolver.constructUsingToString(ctxt.getConfig(), _enumClass())
.constructLookup();
_lookupByToString = lookup;
}
}
}
return lookup;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, my problem is that instead of only creating it if needed, it creates new instance every time and sets -- and never uses value. You are correct in that get + set is not atomic, but I am not worried about atomicity here; if instance gets created 2 or 3 times that's fine. They are all identical. Goal is just to avoid always creating instance eagerly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And no need to do double-locking with synchronized: just do get() from reference; if (and only if) null returned, construct instance, set() (or compareAndSet() against null). Use the props gotten or created.

Or leave as-is and I can add that as well, not a big deal.

Copy link
Member Author

@JooHyukKim JooHyukKim Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, my problem is that instead of only creating it if needed, it creates new instance every time and sets -- and never uses value.

Oh, I think I understand now! Thank you for the explanation! 🙏🏼🙏🏼.

The code creates newly instance in compareAndSet(null, PropertyBasedCreator.construct()). The new intance will be ignored, but created "everytime" when _propCreator is not null anymore.

@JooHyukKim JooHyukKim requested a review from cowtowncoder April 1, 2023 00:55
PropertyBasedCreator.construct(ctxt, _valueInstantiator, _creatorProps,
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)));

if (_propCreatorRef.get() == null) {
Copy link
Member

@cowtowncoder cowtowncoder Apr 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll change this afterwards.... realized that for serializability of deserializer, better to use volatile after all.
Thank you for changes

Copy link
Member Author

@JooHyukKim JooHyukKim Apr 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cowtowncoder I can change it to volatile and handle it like before if you'd like?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JooHyukKim yes, basically I did that; no need to go back and forth. Sorry about confusion.

(the problem with JDK (de)serialization of JsonDeserializers is that we do not want to serialize fields like this, but in case of AtomicReference would need to make sure it's not-null ... volatile is easier, and performance won't matter a lot here-- although to be completely honest I think lazy construction here is probably not a good idea. Could just create eagerly.

Copy link
Member Author

@JooHyukKim JooHyukKim Apr 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JooHyukKim yes,

Sure, I will make a PR soon.·

Oh you did already.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, just the volatile part.

@cowtowncoder cowtowncoder merged commit 3aa2de1 into FasterXML:2.15 Apr 1, 2023
@cowtowncoder cowtowncoder added this to the 2.15.0 milestone Apr 1, 2023
@cowtowncoder
Copy link
Member

Merged: thank you once again @JooHyukKim !

@JooHyukKim
Copy link
Member Author

@cowtowncoder always fun to help~ 🙏🏼🙏🏼

@JooHyukKim JooHyukKim deleted the 3566-fix branch May 22, 2023 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants