Почему компонент Spring State Machine PersistStateChangeListener иногда не выполняется в одном потоке? - PullRequest
3 голосов
/ 10 июля 2020

Короче:

У нас есть реализация Spring State Machine, которая будет сохраняться с другим запросом (в том же потоке из пула потоков) , вызывая ошибку состояния в нашем коде. Почему он не запускается синхронно как часть одного и того же запроса?

Гораздо более длинная история:

Наш конечный автомат настроен так, что когда событие запускается и переход состояния является успешным / действительным и он упорствовал, тогда работа сделана. Если это тоже не удается, мы генерируем исключение. По иронии судьбы, постоянство не применяется в Spring State Machine, поэтому нам пришлось сделать что-то свое, чтобы обеспечить его соблюдение.

У нас есть класс конфигурации:

@Slf4j
@Configuration
@EnableStateMachine(name = "myStateMachine")
public class StatemachineConfiguration extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
     transitions
            .withExternal().source(States.INITIAL.name()).target(States.IN_PROGRESS.name())
            .event(Events.START.name())
            .and()
            ...more transitions
    }

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
        states
            .withStates()
            .initial(States.INITIAL.name())
            .state(States.IN_PROGRESS.name())
            ...more states;
    }

    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception {
        config.withConfiguration()
            .taskExecutor(new SyncTaskExecutor()) // This should be default, but I added it anyways
            .autoStartup(false);
    }
}

Для запуска событий мы используем оболочка, чтобы обеспечить постоянство перед продолжением. Без оболочки мы продолжили работу, фактически не сохранив объект состояния. Это вызывало у нас ошибки перехода между состояниями, когда мы позже пытались инициировать события, но база данных имела старое состояние.

StateEntityWrapper wrapper = new StateEntityWrapper(stateEntity);
boolean success = persistStateMachineHandler.handleEventWithState(
    MessageBuilder.withPayload(event.name()).setHeader(Constants.PERSIST_ENTITY_WRAPPER_HEADER, wrapper).build(),
    stateEntity.getState().name()
        );

if (!(success && wrapper.isPersisted()) {
    throw new StateTransitionException();
}

// Transition saved and successful - begin work
// Trigger other events depending on what happens during work

Наш PersistStateChangeListener:

@Slf4j
@Component
@RequiredArgsConstructor
public class MyPersistStateChangeListener implements PersistStateChangeListener {

    private final MyStateRepository repository;

    @Override
    public void onPersist(State<String, String> state,
                          Message<String> message,
                          Transition<String, String> transition,
                          StateMachine<String, String> stateMachine) {
        StateEntityWrapper wrapper = message.getHeaders().get(Constants.PERSIST_ENTITY_WRAPPER_HEADER, StateEntityWrapper.class);
        if (wrapper != null) {
            StateEntity entity = wrapper.getStateEntity();
            entity.setState(States.valueOf(state.getId()));
            this.persist(entity);
            wrapper.setPersisted(true);
        } else {
            String msg = "StateEntityWrapper is null. Could not retrieve " + Constants.PERSIST_ENTITY_WRAPPER_HEADER + ".";
            log.warn("{}", msg);
            throw new MyPersistRuntimeException(msg);
        }
    }

    private void persist(FulfillmentState entity) {
        try {
            repository.save(entity);
        } catch (Exception e) {
            throw new MyPersistRuntimeException("Persistence failed: " + e.getMessage(), e);
        }
    }
}

I обнаружил, что исключения, создаваемые постоянным слушателем, перехватываются Spring State Machine в AbstractStateMachine::callPreStateChangeInterceptors и пропускают изменение состояния. Однако вызов PersistStateMachineHandler::handleEventWithState вернет true независимо от того, что сохранить невозможно. Это связано с тем, что перехватчики конечного автомата (постоянный прослушиватель является одним из них) можно настроить как асинхронные. Однако, согласно документации, предполагается, что значение по умолчанию будет синхронным.

Если значение по умолчанию синхронно, то почему оно иногда выполняется после возврата PersistStateMachineHandler? Мы используем Sleuth в нашем журнале, и я заметил, что обычно он имеет тот же идентификатор трассировки во время сохранения. Но иногда я вижу, что он выполняется с другим запросом и его идентификатором трассировки. Наконец, я обнаружил, что DefaultStateMachineExecutor управляет TaskExecutor и должен работать синхронно в соответствии с конфигурациями по умолчанию, но иногда этого не происходит. Есть идеи?

...