spring-projects / spring-statemachine

Spring Statemachine is a framework for application developers to use state machine concepts with Spring.
1.52k stars 598 forks source link

Unable to restore statemachine because of sterilization issues from com.esotericsoftware.kryo.KryoException #1059

Open AbstractAlao opened 1 year ago

AbstractAlao commented 1 year ago

When restoring a state machine from the database, I am getting multiple errors from some of my objects I added objects to the state machine like so: stateMachine.getExtendedState().getVariables().put(PARAM_PROCESSOR, data);

When restoring, i get all different types of errors:

com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID: 107
--
Serialization trace:
contributions (org.employmentclient.model.Employment)
employments (org.engine.client.model.ParticipantEmployment)
participantEmployment (org.yretirement.ycap.engine.client.model.LoanRequestContext)
at com.esotericsoftware.kryo.util.DefaultClassResolver.readClass(DefaultClassResolver.java:137) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClass(Kryo.java:693) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:118) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:134) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:40) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:161) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:39) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na]
at org.springframework.statemachine.kryo.StateMachineContextSerializer.read(StateMachineContextSerializer.java:66) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
o.s.s.s.DefaultStateMachineService       : Error handling context
--
 
com.esotericsoftware.kryo.KryoException: java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): -62
Serialization trace:
basicPaymentDate (org.employmentclient.model.Employment)
employments (org.ycap.engine.client.model.ParticipantEmployment)
participantEmployment (org.ycap.engine.client.model.LoanRequestContext)
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:144) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:134) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.CollectionSerializer.read(CollectionSerializer.java:40) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:161) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:39) ~[kryo-shaded-4.0.2.jar:na]
at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na]
at org.springframework.statemachine.kryo.StateMachineContextSerializer.read(StateMachineContextSerializer.java:66) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
at org.springframework.statemachine.kryo.StateMachineContextSerializer.read(StateMachineContextSerializer.java:39) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:709) ~[kryo-shaded-4.0.2.jar:na]
at org.springframework.statemachine.kryo.KryoStateMachineSerialisationService.doDecode(KryoStateMachineSerialisationService.java:45) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService$3.execute(AbstractKryoStateMachineSerialisationService.java:147) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
at com.esotericsoftware.kryo.pool.KryoPoolQueueImpl.run(KryoPoolQueueImpl.java:58) ~[kryo-shaded-4.0.2.jar:na]
at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService.decode(AbstractKryoStateMachineSerialisationService.java:143) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService.decode(AbstractKryoStateMachineSerialisationService.java:130) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService.deserialiseStateMachineContext(AbstractKryoStateMachineSerialisationService.java:72) ~[spring-statemachine-kryo-3.0.0.jar:3.0.0]
AbstractAlao commented 1 year ago

More esoteric messages... `2022-08-17 09:32:59.100 ERROR 98020 --- [nio-8989-exec-9] o.s.s.s.DefaultStateMachineService : Error handling context

com.esotericsoftware.kryo.KryoException: java.lang.IndexOutOfBoundsException: Index 109 out of bounds for length 8 Serialization trace: status (org.yretirement.ycap.engine.client.model.workflow.CancellationRequest) at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:144) ~[kryo-shaded-4.0.2.jar:na] at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-shaded-4.0.2.jar:na] at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na] at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:161) ~[kryo-shaded-4.0.2.jar:na] at com.esotericsoftware.kryo.serializers.MapSerializer.read(MapSerializer.java:39) ~[kryo-shaded-4.0.2.jar:na] at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813) ~[kryo-shaded-4.0.2.jar:na] at org.springframework.statemachine.kryo.StateMachineContextSerializer.read(StateMachineContextSerializer.java:66) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at org.springframework.statemachine.kryo.StateMachineContextSerializer.read(StateMachineContextSerializer.java:39) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:709) ~[kryo-shaded-4.0.2.jar:na] at org.springframework.statemachine.kryo.KryoStateMachineSerialisationService.doDecode(KryoStateMachineSerialisationService.java:45) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService$3.execute(AbstractKryoStateMachineSerialisationService.java:147) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at com.esotericsoftware.kryo.pool.KryoPoolQueueImpl.run(KryoPoolQueueImpl.java:58) ~[kryo-shaded-4.0.2.jar:na] at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService.decode(AbstractKryoStateMachineSerialisationService.java:143) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService.decode(AbstractKryoStateMachineSerialisationService.java:130) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at org.springframework.statemachine.kryo.AbstractKryoStateMachineSerialisationService.deserialiseStateMachineContext(AbstractKryoStateMachineSerialisationService.java:72) ~[spring-statemachine-kryo-3.2.0.jar:3.2.0] at org.springframework.statemachine.data.RepositoryStateMachinePersist.read(RepositoryStateMachinePersist.java:76) ~[spring-statemachine-data-common-3.2.0.jar:3.2.0] at org.springframework.statemachine.data.jpa.JpaPersistingStateMachineInterceptor.read(JpaPersistingStateMachineInterceptor.java:70) ~[spring-statemachine-data-jpa-3.2.0.jar:3.2.0] at org.springframework.statemachine.service.DefaultStateMachineService.acquireStateMachine(DefaultStateMachineService.java:94) ~[spring-statemachine-core-3.2.0.jar:3.2.0] at org.springframework.statemachine.service.DefaultStateMachineService.acquireStateMachine(DefaultStateMachineService.java:79) ~[spring-statemachine-core-3.2.0.jar:3.2.0]`

Aaqib21 commented 1 year ago

I am getting the same error as well. I believe this is due to serialization issue with kryo. stateMachine.getExtendedState().getVariables().put(PARAM_PROCESSOR, data); The model class that your field 'data' refers to might have changed since the last time stateMachine was persisted in DB.

Please let me know if you find a solution.

gustavodaquino commented 1 year ago

Same issue here. I don't know if there is a most recent Kyro version to provide a better approach ignoring properties that don't exist in serialized data.

brunodisanto commented 10 months ago

Same error when context object has its model updated. Added a field and now every state machine older to this change is broken. Anyone going through this? Any solution or workaround?

AbstractAlao commented 10 months ago

This was a pain so I ended up having to not use the extended state and create a JPA implementation and saving my data as json

 create table state_machine_context_data
(
    state_machine_context_data_id bigint identity
        primary key,
    state_machine_id              bigint       not null,
    payload                       nvarchar(max),
    created_date                  datetime
        constraint df_created_state_machine_context_data default getdate(),
    updated_date                  datetime,
    context_class                 varchar(200) not null
)
go

Then used JPA to create the object

import java.io.Serializable;
import javax.persistence.*;
import java.sql.Timestamp;

@Entity
@Table(name="state_machine_context_data")
public class StateMachineContextData implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="state_machine_context_data_id")
    private long stateMachineContextDataId;

    @Column(name="context_class")
    private String contextClass;

    @Column(name="created_date")
    private Timestamp createdDate;

    @Lob
    @Column
    private String payload;

    @Column(name="state_machine_id")
    private long stateMachineId;

    @Column(name="updated_date")
    private Timestamp updatedDate;

    public StateMachineContextData() {
    }

    public long getStateMachineContextDataId() {
        return this.stateMachineContextDataId;
    }

    public void setStateMachineContextDataId(long stateMachineContextDataId) {
        this.stateMachineContextDataId = stateMachineContextDataId;
    }

    public String getContextClass() {
        return this.contextClass;
    }

    public void setContextClass(String contextType) {
        this.contextClass = contextType;
    }

    public Timestamp getCreatedDate() {
        return this.createdDate;
    }

    public void setCreatedDate(Timestamp createdDate) {
        this.createdDate = createdDate;
    }

    public String getPayload() {
        return this.payload;
    }

    public void setPayload(String payload) {
        this.payload = payload;
    }

    public long getStateMachineId() {
        return this.stateMachineId;
    }

    public void setStateMachineId(long stateMachineId) {
        this.stateMachineId = stateMachineId;
    }

    public Timestamp getUpdatedDate() {
        return this.updatedDate;
    }

    public void setUpdatedDate(Timestamp updatedDate) {
        this.updatedDate = updatedDate;
    }

}

Then created a Repository

import java.util.List;
import java.util.Optional;

import org.springframework.data.repository.CrudRepository;
import org.yretirement.ycap.engine.data.table.StateMachineContextData;

public interface StateMachineContextDataRepository extends CrudRepository<StateMachineContextData, Long>{

    List<StateMachineContextData>findByStateMachineId(long stateMachineId);

    Optional<StateMachineContextData> findByStateMachineIdAndContextClass(long stateMachineId, String contextClass);
}

Created a Getter

public  <T> Optional<T> getContextData(long machineId, Class<T> clazz) {

        Optional<StateMachineContextData> context = contextRepository.findByStateMachineIdAndContextClass(machineId, clazz.getCanonicalName());

        if(context.isPresent()) 
            return Optional.of(decompressPayload(context.get(), clazz));
        else
            return Optional.empty();
    }

private <T> T decompressPayload(StateMachineContextData context, Class<T> clazz) {
        Gson gson = new Gson();

        String payload = compressionService.tryDecompress(context.getPayload());

        return gson.fromJson(payload, clazz);
    }

and a setter

public void setContextData(long machineId, Object obj) {
        Optional<StateMachineContextData> context = contextRepository.findByStateMachineIdAndContextClass(machineId, obj.getClass().getCanonicalName());

        StateMachineContextData data = new StateMachineContextData();

        if(context.isPresent()) {
            data = context.get();
            data.setPayload(compressPayload(obj));
            data.setUpdatedDate(new Timestamp(System.currentTimeMillis()));
        }else {

            data.setStateMachineId(machineId);
            data.setPayload(compressPayload(obj));
            data.setContextClass(obj.getClass().getCanonicalName());
            data.setCreatedDate(new Timestamp(System.currentTimeMillis()));
            data.setUpdatedDate(new Timestamp(System.currentTimeMillis()));
        }

        contextRepository.save(data);
    }

And I would use it like so

CancellationRequest request = getContextData(stateMachineId, CancellationRequest.class);

setContextData(stateMachineId, request);

I