From 79f3ce2e9f3563165ee640f917f944ec4b370ac7 Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Mon, 28 May 2012 23:06:00 +0100 Subject: refactor: factor out entity transfer state machine into a separate class to make code more analyzable --- .../EntityTransfer/EntityTransferStateMachine.cs | 269 +++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs (limited to 'OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs') diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs new file mode 100644 index 0000000..d0cab49 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs @@ -0,0 +1,269 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Physics.Manager; +using OpenSim.Services.Interfaces; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; + +namespace OpenSim.Region.CoreModules.Framework.EntityTransfer +{ + /// + /// The possible states that an agent can be in when its being transferred between regions. + /// + /// + /// This is a state machine. + /// + /// [Entry] => Preparing + /// Preparing => { Transferring || CleaningUp || [Exit] } + /// Transferring => { ReceivedAtDestination || CleaningUp } + /// ReceivedAtDestination => CleaningUp + /// CleaningUp => [Exit] + /// + /// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp + /// However, any state can transition to CleaningUp if the teleport has failed. + /// + enum AgentTransferState + { + Preparing, // The agent is being prepared for transfer + Transferring, // The agent is in the process of being transferred to a destination + ReceivedAtDestination, // The destination has notified us that the agent has been successfully received + CleaningUp // The agent is being changed to child/removed after a transfer + } + + /// + /// Records the state of entities when they are in transfer within or between regions (cross or teleport). + /// + public class EntityTransferStateMachine + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// If true then on a teleport, the source region waits for a callback from the destination region. If + /// a callback fails to arrive within a set time then the user is pulled back into the source region. + /// + public bool EnableWaitForAgentArrivedAtDestination { get; set; } + + private EntityTransferModule m_mod; + + private Dictionary m_agentsInTransit = new Dictionary(); + + public EntityTransferStateMachine(EntityTransferModule module) + { + m_mod = module; + } + + /// + /// Set that an agent is in transit. + /// + /// The ID of the agent being teleported + /// true if the agent was not already in transit, false if it was + internal bool SetInTransit(UUID id) + { + lock (m_agentsInTransit) + { + if (!m_agentsInTransit.ContainsKey(id)) + { + m_agentsInTransit[id] = AgentTransferState.Preparing; + return true; + } + } + + return false; + } + + /// + /// Updates the state of an agent that is already in transit. + /// + /// + /// + /// + /// Illegal transitions will throw an Exception + internal void UpdateInTransit(UUID id, AgentTransferState newState) + { + lock (m_agentsInTransit) + { + // Illegal to try and update an agent that's not actually in transit. + if (!m_agentsInTransit.ContainsKey(id)) + throw new Exception( + string.Format( + "Agent with ID {0} is not registered as in transit in {1}", + id, m_mod.Scene.RegionInfo.RegionName)); + + AgentTransferState oldState = m_agentsInTransit[id]; + + bool transitionOkay = false; + + if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) + transitionOkay = true; + else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing) + transitionOkay = true; + else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring) + transitionOkay = true; + + if (transitionOkay) + m_agentsInTransit[id] = newState; + else + throw new Exception( + string.Format( + "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}", + id, oldState, newState, m_mod.Scene.RegionInfo.RegionName)); + } + } + + internal bool IsInTransit(UUID id) + { + lock (m_agentsInTransit) + return m_agentsInTransit.ContainsKey(id); + } + + /// + /// Removes an agent from the transit state machine. + /// + /// + /// true if the agent was flagged as being teleported when this method was called, false otherwise + internal bool ResetFromTransit(UUID id) + { + lock (m_agentsInTransit) + { + if (m_agentsInTransit.ContainsKey(id)) + { + AgentTransferState state = m_agentsInTransit[id]; + + if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination) + { + // FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed + // to be handled properly - ResetFromTransit() could be invoked at any step along the process + m_log.WarnFormat( + "[ENTITY TRANSFER STATE MACHINE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first in {3}", + id, state, AgentTransferState.CleaningUp, m_mod.Scene.RegionInfo.RegionName); + +// throw new Exception( +// "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first", +// state, AgentTransferState.CleaningUp); + } + + m_agentsInTransit.Remove(id); + + m_log.DebugFormat( + "[ENTITY TRANSFER STATE MACHINE]: Agent {0} cleared from transit in {1}", + id, m_mod.Scene.RegionInfo.RegionName); + + return true; + } + } + + m_log.WarnFormat( + "[ENTITY TRANSFER STATE MACHINE]: Agent {0} requested to clear from transit in {1} but was already cleared", + id, m_mod.Scene.RegionInfo.RegionName); + + return false; + } + + internal bool WaitForAgentArrivedAtDestination(UUID id) + { + if (!m_mod.WaitForAgentArrivedAtDestination) + return true; + + lock (m_agentsInTransit) + { + if (!IsInTransit(id)) + throw new Exception( + string.Format( + "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit", + id, m_mod.Scene.RegionInfo.RegionName)); + + AgentTransferState currentState = m_agentsInTransit[id]; + + if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination) + throw new Exception( + string.Format( + "Asked to wait for destination callback for agent with ID {0} in {1} but agent is in state {2}", + id, m_mod.Scene.RegionInfo.RegionName, currentState)); + } + + int count = 200; + + // There should be no race condition here since no other code should be removing the agent transfer or + // changing the state to another other than Transferring => ReceivedAtDestination. + while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0) + { +// m_log.Debug(" >>> Waiting... " + count); + Thread.Sleep(100); + } + + return count > 0; + } + + internal void SetAgentArrivedAtDestination(UUID id) + { + lock (m_agentsInTransit) + { + if (!m_agentsInTransit.ContainsKey(id)) + { + m_log.WarnFormat( + "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but no teleport request is active", + m_mod.Scene.RegionInfo.RegionName, id); + + return; + } + + AgentTransferState currentState = m_agentsInTransit[id]; + + if (currentState == AgentTransferState.ReceivedAtDestination) + { + // An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification. + m_log.WarnFormat( + "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but notification has already previously been received", + m_mod.Scene.RegionInfo.RegionName, id); + } + else if (currentState != AgentTransferState.Transferring) + { + m_log.ErrorFormat( + "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but agent is in state {2}", + m_mod.Scene.RegionInfo.RegionName, id, currentState); + + return; + } + + m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination; + } + } + } +} \ No newline at end of file -- cgit v1.1