From 21cdb40e5984e4fc951f5ca09ff29002b169c778 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 14 Dec 2022 07:16:28 +0000 Subject: [PATCH] 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 --- .../tethering/util/SyncStateMachine.java | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java index cbbbdece66..a17eb269ab 100644 --- a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java +++ b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java @@ -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 mSelfMsgQueue = new ArrayDeque(); + + // 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)); } /**