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/EntityTransferModule.cs | 284 ++++----------------- .../EntityTransfer/EntityTransferStateMachine.cs | 269 +++++++++++++++++++ .../EntityTransfer/HGEntityTransferModule.cs | 16 +- 3 files changed, 330 insertions(+), 239 deletions(-) create mode 100644 OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs (limited to 'OpenSim/Region') diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index ab1424d..f2926ea 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -46,35 +46,12 @@ using Nini.Config; 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 - } - public class EntityTransferModule : INonSharedRegionModule, IEntityTransferModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); public const int DefaultMaxTransferDistance = 4095; - public const bool EnableWaitForCallbackFromTeleportDestDefault = true; + public const bool WaitForAgentArrivedAtDestinationDefault = true; /// /// The maximum distance, in standard region units (256m) that an agent is allowed to transfer. @@ -85,13 +62,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// 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 EnableWaitForCallbackFromTeleportDest { get; set; } + public bool WaitForAgentArrivedAtDestination { get; set; } protected bool m_Enabled = false; - protected Scene m_scene; + public Scene Scene { get; private set; } - private Dictionary m_agentsInTransit; + /// + /// Handles recording and manipulation of state for entities that are in transfer within or between regions + /// (cross or teleport). + /// + private EntityTransferStateMachine m_entityTransferStateMachine; private ExpiringCache> m_bannedRegions = new ExpiringCache>(); @@ -133,8 +114,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer IConfig transferConfig = source.Configs["EntityTransfer"]; if (transferConfig != null) { - EnableWaitForCallbackFromTeleportDest - = transferConfig.GetBoolean("wait_for_callback", EnableWaitForCallbackFromTeleportDestDefault); + WaitForAgentArrivedAtDestination + = transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault); MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance); } @@ -143,7 +124,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer MaxTransferDistance = DefaultMaxTransferDistance; } - m_agentsInTransit = new Dictionary(); + m_entityTransferStateMachine = new EntityTransferStateMachine(this); + m_Enabled = true; } @@ -156,7 +138,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!m_Enabled) return; - m_scene = scene; + Scene = scene; scene.RegisterModuleInterface(this); scene.EventManager.OnNewClient += OnNewClient; @@ -177,7 +159,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!m_Enabled) return; - m_eqModule = m_scene.RequestModuleInterface(); + m_eqModule = Scene.RequestModuleInterface(); } #endregion @@ -226,7 +208,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer e.Message, e.StackTrace); // Make sure that we clear the in-transit flag so that future teleport attempts don't always fail. - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); sp.ControllingClient.SendTeleportFailed("Internal error"); } @@ -245,7 +227,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer "[ENTITY TRANSFER MODULE]: Teleport for {0} to {1} within {2}", sp.Name, position, sp.Scene.RegionInfo.RegionName); - if (!SetInTransit(sp.UUID)) + if (!m_entityTransferStateMachine.SetInTransit(sp.UUID)) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Ignoring within region teleport request of {0} {1} to {2} - agent is already in transit.", @@ -282,7 +264,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer position.Z = newPosZ; } - UpdateInTransit(sp.UUID, AgentTransferState.Transferring); + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); sp.ControllingClient.SendTeleportStart(teleportFlags); @@ -290,15 +272,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer sp.Velocity = Vector3.Zero; sp.Teleport(position); - UpdateInTransit(sp.UUID, AgentTransferState.ReceivedAtDestination); + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.ReceivedAtDestination); foreach (SceneObjectGroup grp in sp.GetAttachments()) { sp.Scene.EventManager.TriggerOnScriptChangedEvent(grp.LocalId, (uint)Changed.TELEPORT); } - UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); } /// @@ -316,7 +298,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { uint x = 0, y = 0; Utils.LongToUInts(regionHandle, out x, out y); - GridRegion reg = m_scene.GridService.GetRegionByPosition(sp.Scene.RegionInfo.ScopeID, (int)x, (int)y); + GridRegion reg = Scene.GridService.GetRegionByPosition(sp.Scene.RegionInfo.ScopeID, (int)x, (int)y); if (reg != null) { @@ -398,7 +380,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer { // Record that this agent is in transit so that we can prevent simultaneous requests and do later detection // of whether the destination region completes the teleport. - if (!SetInTransit(sp.UUID)) + if (!m_entityTransferStateMachine.SetInTransit(sp.UUID)) { m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Ignoring teleport request of {0} {1} to {2} ({3}) {4}/{5} - agent is already in transit.", @@ -410,7 +392,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (reg == null || finalDestination == null) { sp.ControllingClient.SendTeleportFailed("Unable to locate destination"); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); return; } @@ -431,7 +413,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY, MaxTransferDistance)); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); return; } @@ -451,7 +433,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (endPoint.Address == null) { sp.ControllingClient.SendTeleportFailed("Remote Region appears to be down"); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); return; } @@ -469,11 +451,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer string reason; string version; - if (!m_scene.SimulationService.QueryAccess( + if (!Scene.SimulationService.QueryAccess( finalDestination, sp.ControllingClient.AgentId, Vector3.Zero, out version, out reason)) { sp.ControllingClient.SendTeleportFailed(reason); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: {0} was stopped from teleporting from {1} to {2} because {3}", @@ -527,7 +509,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) { sp.ControllingClient.SendTeleportFailed(String.Format("Teleport refused: {0}", reason)); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Teleport of {0} from {1} to {2} was refused because {3}", @@ -537,7 +519,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } // Past this point we have to attempt clean up if the teleport fails, so update transfer state. - UpdateInTransit(sp.UUID, AgentTransferState.Transferring); + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); // OK, it got this agent. Let's close some child agents sp.CloseChildAgents(newRegionX, newRegionY); @@ -619,7 +601,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // TeleportFinish makes the client send CompleteMovementIntoRegion (at the destination), which // trigers a whole shebang of things there, including MakeRoot. So let's wait for confirmation // that the client contacted the destination before we close things here. - if (EnableWaitForCallbackFromTeleportDest && !WaitForCallback(sp.UUID)) + if (!m_entityTransferStateMachine.WaitForAgentArrivedAtDestination(sp.UUID)) { m_log.WarnFormat( "[ENTITY TRANSFER MODULE]: Teleport of {0} to {1} from {2} failed due to no callback from destination region. Returning avatar to source region.", @@ -629,7 +611,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } - UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); // For backwards compatibility if (version == "Unknown" || version == string.Empty) @@ -679,12 +661,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer sp.UUID); } - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); } protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) { - UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); + m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); // Client never contacted destination. Let's restore everything back sp.ControllingClient.SendTeleportFailed("Problems connecting to destination."); @@ -696,17 +678,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer EnableChildAgents(sp); // Finally, kill the agent we just created at the destination. - m_scene.SimulationService.CloseAgent(finalDestination, sp.UUID); + Scene.SimulationService.CloseAgent(finalDestination, sp.UUID); sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout); - ResetFromTransit(sp.UUID); + m_entityTransferStateMachine.ResetFromTransit(sp.UUID); } protected virtual bool CreateAgent(ScenePresence sp, GridRegion reg, GridRegion finalDestination, AgentCircuitData agentCircuit, uint teleportFlags, out string reason, out bool logout) { logout = false; - bool success = m_scene.SimulationService.CreateAgent(finalDestination, agentCircuit, teleportFlags, out reason); + bool success = Scene.SimulationService.CreateAgent(finalDestination, agentCircuit, teleportFlags, out reason); if (success) sp.Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout); @@ -716,7 +698,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer protected virtual bool UpdateAgent(GridRegion reg, GridRegion finalDestination, AgentData agent) { - return m_scene.SimulationService.UpdateAgent(finalDestination, agent); + return Scene.SimulationService.UpdateAgent(finalDestination, agent); } protected virtual void SetCallbackURL(AgentData agent, RegionInfo region) @@ -779,7 +761,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// public virtual void RequestTeleportLandmark(IClientAPI remoteClient, AssetLandmark lm) { - GridRegion info = m_scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID); + GridRegion info = Scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID); if (info == null) { @@ -800,12 +782,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId); - //OpenSim.Services.Interfaces.PresenceInfo pinfo = m_scene.PresenceService.GetAgent(client.SessionId); - GridUserInfo uinfo = m_scene.GridUserService.GetGridUserInfo(client.AgentId.ToString()); + //OpenSim.Services.Interfaces.PresenceInfo pinfo = Scene.PresenceService.GetAgent(client.SessionId); + GridUserInfo uinfo = Scene.GridUserService.GetGridUserInfo(client.AgentId.ToString()); if (uinfo != null) { - GridRegion regionInfo = m_scene.GridService.GetRegionByUUID(UUID.Zero, uinfo.HomeRegionID); + GridRegion regionInfo = Scene.GridService.GetRegionByUUID(UUID.Zero, uinfo.HomeRegionID); if (regionInfo == null) { // can't find the Home region: Tell viewer and abort @@ -1114,7 +1096,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer try { - SetInTransit(agent.UUID); + m_entityTransferStateMachine.SetInTransit(agent.UUID); ulong neighbourHandle = Utils.UIntsToLong((uint)(neighbourx * Constants.RegionSize), (uint)(neighboury * Constants.RegionSize)); @@ -1144,16 +1126,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer cAgent.CallbackURI = String.Empty; // Beyond this point, extra cleanup is needed beyond removing transit state - UpdateInTransit(agent.UUID, AgentTransferState.Transferring); + m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.Transferring); if (!m_scene.SimulationService.UpdateAgent(neighbourRegion, cAgent)) { // region doesn't take it - UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); + m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); ReInstantiateScripts(agent); agent.AddToPhysicalScene(isFlying); - ResetFromTransit(agent.UUID); + m_entityTransferStateMachine.ResetFromTransit(agent.UUID); return agent; } @@ -1191,16 +1173,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } // SUCCESS! - UpdateInTransit(agent.UUID, AgentTransferState.ReceivedAtDestination); + m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.ReceivedAtDestination); // Unlike a teleport, here we do not wait for the destination region to confirm the receipt. - UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); + m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); agent.MakeChildAgent(); // FIXME: Possibly this should occur lower down after other commands to close other agents, // but not sure yet what the side effects would be. - ResetFromTransit(agent.UUID); + m_entityTransferStateMachine.ResetFromTransit(agent.UUID); // now we have a child agent in this region. Request all interesting data about other (root) agents agent.SendOtherAgentsAvatarDataToMe(); @@ -1675,41 +1657,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer #endregion - #region Agent Arrived + public void AgentArrivedAtDestination(UUID id) { - lock (m_agentsInTransit) - { - if (!m_agentsInTransit.ContainsKey(id)) - { - m_log.WarnFormat( - "[ENTITY TRANSFER MODULE]: Region {0} received notification of arrival in destination scene of agent {1} but no teleport request is active", - m_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 MODULE]: Region {0} received notification of arrival in destination scene of agent {1} but notification has already previously been received", - m_scene.RegionInfo.RegionName, id); - } - else if (currentState != AgentTransferState.Transferring) - { - m_log.ErrorFormat( - "[ENTITY TRANSFER MODULE]: Region {0} received notification of arrival in destination scene of agent {1} but agent is in transfer state {2}", - m_scene.RegionInfo.RegionName, id, currentState); - - return; - } - - m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination; - } + m_entityTransferStateMachine.SetAgentArrivedAtDestination(id); } #endregion @@ -1979,8 +1931,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer //// And the new channel... //if (m_interregionCommsOut != null) // successYN = m_interregionCommsOut.SendCreateObject(newRegionHandle, grp, true); - if (m_scene.SimulationService != null) - successYN = m_scene.SimulationService.CreateObject(destination, newPosition, grp, true); + if (Scene.SimulationService != null) + successYN = Scene.SimulationService.CreateObject(destination, newPosition, grp, true); if (successYN) { @@ -2051,139 +2003,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer #region Misc - private bool WaitForCallback(UUID id) - { - lock (m_agentsInTransit) - { - if (!IsInTransit(id)) - throw new Exception( - string.Format( - "Asked to wait for destination callback for agent with ID {0} but it is not in transit")); - - 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} but it is in state {1}", - 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; - } - - /// - /// 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 - private 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 - private 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", id)); - - 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}", - id, oldState, newState)); - } - } - public 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 - private 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 MODULE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first", - id, state, AgentTransferState.CleaningUp); - -// 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 MODULE]: Agent {0} cleared from transit in {1}", - id, m_scene.RegionInfo.RegionName); - - return true; - } - } - - m_log.WarnFormat( - "[ENTITY TRANSFER MODULE]: Agent {0} requested to clear from transit in {1} but was already cleared.", - id, m_scene.RegionInfo.RegionName); - - return false; + return m_entityTransferStateMachine.IsInTransit(id); } protected void ReInstantiateScripts(ScenePresence sp) 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 diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs index 0fdd457..43a72e2 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs @@ -117,7 +117,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer protected override GridRegion GetFinalDestination(GridRegion region) { - int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, region.RegionID); + int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, region.RegionID); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: region {0} flags: {1}", region.RegionID, flags); if ((flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0) @@ -139,7 +139,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (base.NeedsClosing(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) return true; - int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, reg.RegionID); + int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, reg.RegionID); if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0) return true; @@ -152,7 +152,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (logout) { // Log them out of this grid - m_scene.PresenceService.LogoutAgent(sp.ControllingClient.SessionId); + Scene.PresenceService.LogoutAgent(sp.ControllingClient.SessionId); } } @@ -161,7 +161,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: CreateAgent {0} {1}", reg.ServerURI, finalDestination.ServerURI); reason = string.Empty; logout = false; - int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, reg.RegionID); + int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, reg.RegionID); if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0) { // this user is going to another grid @@ -201,7 +201,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer "[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId); // Let's find out if this is a foreign user or a local user - IUserManagement uMan = m_scene.RequestModuleInterface(); + IUserManagement uMan = Scene.RequestModuleInterface(); if (uMan != null && uMan.IsLocalGridUser(id)) { // local grid user @@ -265,7 +265,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } - GridRegion info = m_scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID); + GridRegion info = Scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID); // Local region? if (info != null) @@ -335,8 +335,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer } // Let's find out if this is a foreign user or a local user - IUserManagement uMan = m_scene.RequestModuleInterface(); -// UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, obj.AgentId); + IUserManagement uMan = Scene.RequestModuleInterface(); +// UserAccount account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, obj.AgentId); if (uMan != null && uMan.IsLocalGridUser(obj.AgentId)) { // local grid user -- cgit v1.1