From 8f87f55d053a69bf1f5b2758d51871c4c76e15b9 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 28 May 2012 22:14:37 +0100 Subject: [PATCH 1/3] If handling the failure of teleport, move agent state to CleaningUp when we start the handling. Also fixes the log warning from ResetInTransit() if the state is cleared direct from Transferring or ReceiveAtDestination, as pointed out in mantis 5426 --- .../Framework/EntityTransfer/EntityTransferModule.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index ae9fb53aa3..ab1424d2db 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -684,13 +684,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) { + UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); + // Client never contacted destination. Let's restore everything back sp.ControllingClient.SendTeleportFailed("Problems connecting to destination."); // Fail. Reset it back sp.IsChildAgent = false; ReInstantiateScripts(sp); - ResetFromTransit(sp.UUID); EnableChildAgents(sp); @@ -698,6 +699,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer m_scene.SimulationService.CloseAgent(finalDestination, sp.UUID); sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout); + + ResetFromTransit(sp.UUID); } protected virtual bool CreateAgent(ScenePresence sp, GridRegion reg, GridRegion finalDestination, AgentCircuitData agentCircuit, uint teleportFlags, out string reason, out bool logout) @@ -2158,8 +2161,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // 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 should not exit directly from state {1}, should go to {2} state first", - state, AgentTransferState.CleaningUp); + "[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", From 79f3ce2e9f3563165ee640f917f944ec4b370ac7 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 28 May 2012 23:06:00 +0100 Subject: [PATCH 2/3] refactor: factor out entity transfer state machine into a separate class to make code more analyzable --- .../EntityTransfer/EntityTransferModule.cs | 284 ++++-------------- .../EntityTransferStateMachine.cs | 269 +++++++++++++++++ .../EntityTransfer/HGEntityTransferModule.cs | 16 +- 3 files changed, 330 insertions(+), 239 deletions(-) create mode 100644 OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index ab1424d2db..f2926eaf1f 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 0000000000..d0cab49563 --- /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 0fdd4578a9..43a72e279d 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 From b660c4991bf5982bc22690357e2dd6a4b015d9e9 Mon Sep 17 00:00:00 2001 From: Melanie Date: Tue, 29 May 2012 18:18:47 +0100 Subject: [PATCH 3/3] Fix collision filtering. The filter should be checked on the receiving part! --- .../Region/Framework/Scenes/SceneObjectPart.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index dc76d22931..17c766191f 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -2023,21 +2023,21 @@ namespace OpenSim.Region.Framework.Scenes { } - private bool CollisionFilteredOut(SceneObjectPart dest, UUID objectID, string objectName) + public bool CollisionFilteredOut(UUID objectID, string objectName) { - if(dest.CollisionFilter.Count == 0) + if(CollisionFilter.Count == 0) return false; - if (dest.CollisionFilter.ContainsValue(objectID.ToString()) || - dest.CollisionFilter.ContainsValue(objectID.ToString() + objectName) || - dest.CollisionFilter.ContainsValue(UUID.Zero.ToString() + objectName)) + if (CollisionFilter.ContainsValue(objectID.ToString()) || + CollisionFilter.ContainsValue(objectID.ToString() + objectName) || + CollisionFilter.ContainsValue(UUID.Zero.ToString() + objectName)) { - if (dest.CollisionFilter.ContainsKey(1)) + if (CollisionFilter.ContainsKey(1)) return false; return true; } - if (dest.CollisionFilter.ContainsKey(1)) + if (CollisionFilter.ContainsKey(1)) return true; return false; @@ -2100,7 +2100,7 @@ namespace OpenSim.Region.Framework.Scenes SceneObjectPart obj = ParentGroup.Scene.GetSceneObjectPart(localId); if (obj != null) { - if (!dest.CollisionFilteredOut(this, obj.UUID, obj.Name)) + if (!dest.CollisionFilteredOut(obj.UUID, obj.Name)) colliding.Add(CreateDetObject(obj)); } else @@ -2108,7 +2108,7 @@ namespace OpenSim.Region.Framework.Scenes ScenePresence av = ParentGroup.Scene.GetScenePresence(localId); if (av != null && (!av.IsChildAgent)) { - if (!dest.CollisionFilteredOut(this, av.UUID, av.Name)) + if (!dest.CollisionFilteredOut(av.UUID, av.Name)) colliding.Add(CreateDetObject(av)); } }