diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 65df6814c8..fd04722a61 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -146,6 +146,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer protected virtual void OnNewClient(IClientAPI client) { + client.OnTeleportCancel += OnClientCancelTeleport; client.OnTeleportHomeRequest += TeleportHome; client.OnTeleportLandmarkRequest += RequestTeleportLandmark; } @@ -166,6 +167,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer #region Agent Teleports + private void OnClientCancelTeleport(IClientAPI client) + { + m_entityTransferStateMachine.UpdateInTransit(client.AgentId, AgentTransferState.Cancelling); + + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Received teleport cancel request from {0} in {1}", client.Name, Scene.Name); + } + public void Teleport(ScenePresence sp, ulong regionHandle, Vector3 position, Vector3 lookAt, uint teleportFlags) { if (sp.Scene.Permissions.IsGridGod(sp.UUID)) @@ -550,6 +559,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Cancelled teleport of {0} to {1} from {2} after CreateAgent on client request", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + return; + } + // Past this point we have to attempt clean up if the teleport fails, so update transfer state. m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); @@ -614,7 +632,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } - sp.ControllingClient.SendTeleportProgress(teleportFlags | (uint)TeleportFlags.DisableCancel, "sending_dest"); + if (m_entityTransferStateMachine.GetAgentTransferState(sp.UUID) == AgentTransferState.Cancelling) + { + m_log.DebugFormat( + "[ENTITY TRANSFER MODULE]: Cancelled teleport of {0} to {1} from {2} after UpdateAgent on client request", + sp.Name, finalDestination.RegionName, sp.Scene.Name); + + CleanupAbortedInterRegionTeleport(sp, finalDestination); + + return; + } m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} from {1} to {2}", @@ -697,14 +724,19 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // } } - protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) + /// + /// Clean up an inter-region teleport that did not complete, either because of simulator failure or cancellation. + /// + /// + /// All operations here must be idempotent so that we can call this method at any point in the teleport process + /// up until we send the TeleportFinish event quene event to the viewer. + /// + /// + /// + protected virtual void CleanupAbortedInterRegionTeleport(ScenePresence sp, GridRegion finalDestination) { m_entityTransferStateMachine.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); @@ -712,7 +744,20 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Finally, kill the agent we just created at the destination. Scene.SimulationService.CloseAgent(finalDestination, sp.UUID); + } + /// + /// Signal that the inter-region teleport failed and perform cleanup. + /// + /// + /// + /// + protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) + { + CleanupAbortedInterRegionTeleport(sp, finalDestination); + + sp.ControllingClient.SendTeleportFailed( + string.Format("Problems connecting to destination {0}", finalDestination.RegionName)); sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout); } @@ -2068,7 +2113,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer public bool IsInTransit(UUID id) { - return m_entityTransferStateMachine.IsInTransit(id); + return m_entityTransferStateMachine.GetAgentTransferState(id) != null; } protected void ReInstantiateScripts(ScenePresence sp) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs index d0cab49563..24d81d9480 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs @@ -51,8 +51,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// This is a state machine. /// /// [Entry] => Preparing - /// Preparing => { Transferring || CleaningUp || [Exit] } - /// Transferring => { ReceivedAtDestination || CleaningUp } + /// Preparing => { Transferring || Cancelling || CleaningUp || [Exit] } + /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp } + /// Cancelling => CleaningUp /// ReceivedAtDestination => CleaningUp /// CleaningUp => [Exit] /// @@ -64,7 +65,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer 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 + CleaningUp, // The agent is being changed to child/removed after a transfer + Cancelling // The user has cancelled the teleport but we have yet to act upon this. } /// @@ -115,42 +117,110 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// /// /// Illegal transitions will throw an Exception - internal void UpdateInTransit(UUID id, AgentTransferState newState) + internal bool UpdateInTransit(UUID id, AgentTransferState newState) { + bool transitionOkay = false; + + // We don't want to throw an exception on cancel since this can come it at any time. + bool failIfNotOkay = true; + + // Should be a failure message if failure is not okay. + string failureMessage = null; + + AgentTransferState? oldState = null; + 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)); + { + if (newState != AgentTransferState.Cancelling) + failureMessage = string.Format( + "Agent with ID {0} is not registered as in transit in {1}", + id, m_mod.Scene.RegionInfo.RegionName); + else + failIfNotOkay = false; + } + else + { + oldState = m_agentsInTransit[id]; - AgentTransferState oldState = m_agentsInTransit[id]; + 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; + } + else + { + if (newState == AgentTransferState.Cancelling + && (oldState == AgentTransferState.Preparing || oldState == AgentTransferState.Transferring)) + { + transitionOkay = true; + } + else + { + failIfNotOkay = false; + } + } - 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) + failureMessage + = 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); + } 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)); + +// m_log.DebugFormat( +// "[ENTITY TRANSFER STATE MACHINE]: Changed agent with id {0} from state {1} to {2} in {3}", +// id, oldState, newState, m_mod.Scene.Name); + } + else if (failIfNotOkay) + { + throw new Exception(failureMessage); + } +// else +// { +// if (oldState != null) +// m_log.DebugFormat( +// "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} from state {1} to {2} in {3}", +// id, oldState, newState, m_mod.Scene.Name); +// else +// m_log.DebugFormat( +// "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} to state {1} in {2} since agent not in transit", +// id, newState, m_mod.Scene.Name); +// } } + + return transitionOkay; } - internal bool IsInTransit(UUID id) + /// + /// Gets the current agent transfer state. + /// + /// Null if the agent is not in transit + /// + /// Identifier. + /// + internal AgentTransferState? GetAgentTransferState(UUID id) { lock (m_agentsInTransit) - return m_agentsInTransit.ContainsKey(id); + { + if (!m_agentsInTransit.ContainsKey(id)) + return null; + else + return m_agentsInTransit[id]; + } } /// @@ -203,14 +273,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer lock (m_agentsInTransit) { - if (!IsInTransit(id)) + AgentTransferState? currentState = GetAgentTransferState(id); + + if (currentState == null) 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(