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

Deserialization fails with "Buffer underflow" after add a new field to record class (Issue with backward compatibility) #1012

Open
investr777 opened this issue Sep 26, 2023 · 3 comments
Labels

Comments

@investr777
Copy link

investr777 commented Sep 26, 2023

Kryo version 5.5.0
Java 17

When data was serialized and after that a new field was added, deserilization fails with exception: com.esotericsoftware.kryo.kryo5.io.KryoBufferUnderflowException: Buffer underflow
Stacktrace

Exception in thread "main" com.esotericsoftware.kryo.kryo5.io.KryoBufferUnderflowException: Buffer underflow.
Serialization trace:
info (akta.dcs.session.live.hash.Data)
	at com.esotericsoftware.kryo.kryo5.io.Input.require(Input.java:221)
	at com.esotericsoftware.kryo.kryo5.io.Input.readVarIntFlag(Input.java:482)
	at com.esotericsoftware.kryo.kryo5.io.Input.readString(Input.java:777)
	at com.esotericsoftware.kryo.kryo5.serializers.DefaultSerializers$StringSerializer.read(DefaultSerializers.java:174)
	at com.esotericsoftware.kryo.kryo5.serializers.DefaultSerializers$StringSerializer.read(DefaultSerializers.java:164)
	at com.esotericsoftware.kryo.kryo5.Kryo.readObjectOrNull(Kryo.java:826)
	at com.esotericsoftware.kryo.kryo5.serializers.RecordSerializer.read(RecordSerializer.java:136)
	at com.esotericsoftware.kryo.kryo5.Kryo.readObject(Kryo.java:777)

Code example:

public record Data(
    String id
){}

===============================================
var kryoLocal = ThreadLocal.withInitial(() -> {
    Kryo kryo = new Kryo();
    kryo.setRegistrationRequired(false);
    kryo.setDefaultSerializer(CompatibleFieldSerializer.class);
    return kryo;
});

 var data = new Data("1");

  var output = new Output(1024, -1);
  kryoLocal.get().writeObject(output, data);
  output.flush();
  var serializedData = output.toBytes(); // [-126, 49]
  output.close();

After serialized, new field was added:

public record Data(
    String id,
   String info
){}

=================

var input = new Input(new byte[] {-126, 49});
var deserializedData = kryoLocal.get().readObject(input, Data.class);
input.close();

And eventually an exception is thrown: com.esotericsoftware.kryo.kryo5.io.KryoBufferUnderflowException: Buffer underflow.

But if convert Java record to class, everything works properly:
Code example:

public class Data {
    private String id;

    public Data() {
    }

    public Data(
        String id
    ) {
        this.id = id;
    }

    public String id() {
        return id;
    }
}

=====================================
var kryoLocal = ThreadLocal.withInitial(() -> {
            Kryo kryo = new Kryo();
            kryo.setRegistrationRequired(false);
            kryo.setDefaultSerializer(CompatibleFieldSerializer.class);
            return kryo;
        });

        var data = new Data("1");

        var output = new Output(1024, -1);
        kryoLocal.get().writeObject(output, data);
        output.flush();
        var serializedData = output.toBytes(); // [1, 105, -28, 3, -126, 49]
        output.close();

After serialized, new field was added:

public class Data {
    private String id;
    private String info;

    public Data() {
    }

    public Data(
        String id,
        String info
    ) {
        this.id = id;
        this.info = info;
    }

    public String id() {
        return id;
    }

    public String info() {
        return info;
    }
}

==========================================

var input = new Input(new byte[] {1, 105, -28, 3, -126, 49});
var deserializedData = kryoLocal.get().readObject(input, Data.class);
input.close();

As we can see, that serialized record and class have different bytes.

@investr777 investr777 changed the title Deserialization fails with "Buffer underflow" after add a new field (Issue with backward compatibility) Deserialization fails with "Buffer underflow" after add a new field to record class (Issue with backward compatibility) Oct 3, 2023
@theigl
Copy link
Collaborator

theigl commented Oct 16, 2023

@investr777: Thanks for the detailed report and sorry for the late answer!

The RecordSerializer currently does not have support for backwards/forwards compatibility. It is a limitation in its design that I realized only after it was originally contributed by Oracle.

See this issue for a more detailed discussion of the problem.

I have created a POC that integrates record serialization back into the default FieldSerializer and its subclasses. I ran your test-case against this PR and it passes. it will be part of the next major release, Kryo 6.

I'm afraid that for Kryo 5 there is not much we can do to fix this. If you need support for this right now and can't switch to normal classes for cases where you need compatibility, you could write your own record serializer that uses the same logic as CompatibleFieldSerializer.

@investr777
Copy link
Author

investr777 commented Nov 2, 2023

@theigl, thank you so much for your reply.
Will Kryo 6 and Kryo 5 have backward compatibility? Will Kryo 6 be able to deserialized data, which were serialized by Kryo 5?

@theigl
Copy link
Collaborator

theigl commented Nov 27, 2023

@investr777: Yes, my goal is that Kryo 6 is backwards compatible (See https://github.com/EsotericSoftware/kryo/wiki/Kryo-v6-Ideas).

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

No branches or pull requests

2 participants