SyncSM04: implement processMessage function
After this change, all synchronous state machine functions are implemented. There are some intentional limiations: 1. Allow setup states before machine thread started or inside machine thread but states can only be setup once before calling #start. 2. The behavior of transitionTo is defined so that the destination state can never change during state transitions. Once a destination has been specified, it cannot be changed until the previous state transition is complete. In other words, SyncStateMachine does not support calling transitionTo in State enter or exit. 3. Support sendSelfMessage in State#processMessage(), #enter() and #exit(). This allow automaton to decide the follow up action by itself. For example: if something wrong happens during state transition, the implementation can enqueue an error message then process the error message after finishing the current state transition. Test: atest SynStateMachineTest Change-Id: I0790df4eeab2dccdb9f407d9131a62c3c12d123b
This commit is contained in:
@@ -18,12 +18,14 @@ package com.android.networkstack.tethering.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Message;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.State;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -48,6 +50,14 @@ public class SyncStateMachine {
|
||||
// mDestState only be null before state machine starts and must only be touched on mMyThread.
|
||||
@Nullable private State mCurrentState;
|
||||
@Nullable private State mDestState;
|
||||
private final ArrayDeque<Message> mSelfMsgQueue = new ArrayDeque<Message>();
|
||||
|
||||
// MIN_VALUE means not currently processing any message.
|
||||
private int mCurrentlyProcessing = Integer.MIN_VALUE;
|
||||
// Indicates whether automaton can send self message. Self messages can only be sent by
|
||||
// automaton from State#enter, State#exit, or State#processMessage. Calling from outside
|
||||
// of State is not allowed.
|
||||
private boolean mSelfMsgAllowed = false;
|
||||
|
||||
/**
|
||||
* A information class about a state and its parent. Used to maintain the state hierarchy.
|
||||
@@ -141,16 +151,87 @@ public class SyncStateMachine {
|
||||
ensureExistingState(initialState);
|
||||
|
||||
mDestState = initialState;
|
||||
mSelfMsgAllowed = true;
|
||||
performTransitions();
|
||||
mSelfMsgAllowed = false;
|
||||
// If sendSelfMessage was called inside initialState#enter(), mSelfMsgQueue must be
|
||||
// processed.
|
||||
maybeProcessSelfMessageQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the message synchronously then perform state transition.
|
||||
* Process the message synchronously then perform state transition. This method is used
|
||||
* externally to the automaton to request that the automaton process the given message.
|
||||
* The message is processed sequentially, so calling this method recursively is not permitted.
|
||||
* In other words, using this method inside State#enter, State#exit, or State#processMessage
|
||||
* is incorrect and will result in an IllegalStateException.
|
||||
*/
|
||||
public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) {
|
||||
ensureCorrectThread();
|
||||
|
||||
if (mCurrentlyProcessing != Integer.MIN_VALUE) {
|
||||
throw new IllegalStateException("Message(" + mCurrentlyProcessing
|
||||
+ ") is still being processed");
|
||||
}
|
||||
|
||||
// mCurrentlyProcessing tracks the external message request and it prevents this method to
|
||||
// be called recursively. Once this message is processed and the transitions have been
|
||||
// performed, the automaton will process the self message queue. The messages in the self
|
||||
// message queue are added from within the automaton during processing external message.
|
||||
// mCurrentlyProcessing is still the original external one and it will not prevent self
|
||||
// messages from being processed.
|
||||
mCurrentlyProcessing = what;
|
||||
final Message msg = Message.obtain(null, what, arg1, arg2, obj);
|
||||
currentStateProcessMessageThenPerformTransitions(msg);
|
||||
msg.recycle();
|
||||
maybeProcessSelfMessageQueue();
|
||||
|
||||
mCurrentlyProcessing = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
private void maybeProcessSelfMessageQueue() {
|
||||
while (!mSelfMsgQueue.isEmpty()) {
|
||||
currentStateProcessMessageThenPerformTransitions(mSelfMsgQueue.poll());
|
||||
}
|
||||
}
|
||||
|
||||
private void currentStateProcessMessageThenPerformTransitions(@NonNull final Message msg) {
|
||||
mSelfMsgAllowed = true;
|
||||
StateInfo consideredState = mStateInfo.get(mCurrentState);
|
||||
while (null != consideredState) {
|
||||
// Ideally this should compare with IState.HANDLED, but it is not public field so just
|
||||
// checking whether the return value is true (IState.HANDLED = true).
|
||||
if (consideredState.state.processMessage(msg)) {
|
||||
if (mDbg) {
|
||||
Log.d(mName, "State " + consideredState.state
|
||||
+ " processed message " + msg.what);
|
||||
}
|
||||
break;
|
||||
}
|
||||
consideredState = mStateInfo.get(consideredState.parent);
|
||||
}
|
||||
if (null == consideredState) {
|
||||
Log.wtf(mName, "Message " + msg.what + " was not handled");
|
||||
}
|
||||
|
||||
performTransitions();
|
||||
mSelfMsgAllowed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send self message during state transition.
|
||||
*
|
||||
* Must only be used inside State processMessage, enter or exit. The typical use case is
|
||||
* something wrong happens during state transition, sending an error message which would be
|
||||
* handled after finishing current state transitions.
|
||||
*/
|
||||
public final void sendSelfMessage(int what, int arg1, int arg2, Object obj) {
|
||||
if (!mSelfMsgAllowed) {
|
||||
throw new IllegalStateException("sendSelfMessage can only be called inside "
|
||||
+ "State#enter, State#exit or State#processMessage");
|
||||
}
|
||||
|
||||
mSelfMsgQueue.add(Message.obtain(null, what, arg1, arg2, obj));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user