Skip to content
Snippets Groups Projects
Commit 21cdb40e authored by Mark's avatar Mark
Browse files

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
parent 9e572d10
No related branches found
No related tags found
No related merge requests found
......@@ -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));
}
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment