hekailiang / squirrel

squirrel-foundation is a State Machine library, which provided a lightweight, easy use, type safe and programmable state machine implementation for Java.
http://hekailiang.github.io/squirrel/
Other
2.19k stars 540 forks source link

ConcurrentModificationException on event firing #130

Open azhuchkov opened 3 years ago

azhuchkov commented 3 years ago

Sometimes (rarely) I encounter the following exception:

java.util.ConcurrentModificationException: null
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) ~[na:1.8.0_161]
    at java.util.ArrayList$Itr.next(ArrayList.java:859) ~[na:1.8.0_161]
    at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1042) ~[na:1.8.0_161]
    at org.squirrelframework.foundation.fsm.impl.StateImpl.entry(StateImpl.java:128) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.TimedStateImpl.entry(TimedStateImpl.java:17) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.StateImpl.enterShallow(StateImpl.java:271) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.TimedStateImpl.enterShallow(TimedStateImpl.java:17) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.StateImpl.enterHistoryNone(StateImpl.java:295) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.StateImpl.enterByHistory(StateImpl.java:248) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.TransitionImpl.internalFire(TransitionImpl.java:203) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:419) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.TimedStateImpl.internalFire(TimedStateImpl.java:17) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.processEvent(AbstractStateMachine.java:161) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.processEvents(AbstractStateMachine.java:215) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.internalFire(AbstractStateMachine.java:248) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.fire(AbstractStateMachine.java:268) ~[squirrel-foundation-0.3.8.jar:na]
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.fire(AbstractStateMachine.java:279) ~[squirrel-foundation-0.3.8.jar:na]

Since I am unable to find code paths of action list modifications during FSM execution, I guess this is a memory visibility issue. The quick fix I use:

  static {
    // must be called before org.squirrelframework.foundation.fsm.impl.FSM.newActions()
    SquirrelProvider.getInstance().register(Actions.class, ThreadSafeActionsImpl.class);
  }

  public static class ThreadSafeActionsImpl<T extends StateMachine<T, S, E, C>, S, E, C>
      implements Actions<T, S, E, C> {

    private static final Comparator<Action<?, ?, ?, ?>> ACTION_COMPARATOR =
        (o1, o2) -> o2.weight() - o1.weight();

    private final SortedSet<Action<T, S, E, C>> actions =
        Collections.synchronizedSortedSet(new TreeSet<>(ACTION_COMPARATOR));

    @Override
    public void add(Action<T, S, E, C> newAction) {
      actions.add(newAction);
    }

    @Override
    public void addAll(List<? extends Action<T, S, E, C>> newActions) {
      actions.addAll(newActions);
    }

    @Override
    public List<Action<T, S, E, C>> getAll() {
      return Collections.unmodifiableList(new ArrayList<>(actions));
    }

    @Override
    public void clear() {
      throw new UnsupportedOperationException("not implemented");
    }
  }