Improve teleport cancellation in some circumstances, though cancelling teleports is still not recommended.

Previously, hitting the cancel button on a teleport would cancel on the client side but the request was ignored on the server side.
Cancel would still work if the teleport failed in the early stages (e.g. because the destination never replied to early CreateAgent and UpdateAgent messages).
But if the teleport still completed after a delay here or later on, the viewer would become confused (usual symptom appears to be avatar being unable to move/reteleport).
This commit makes OpenSimulator obey cancellations which are received before it sends the TeleportFinish event queue message and does proper cleanup.
But cancellations received after this (which can happen even though the cancel button is removed as this messages comes on a different thread) can still result in a frozen avatar.
This looks extremely difficult and impossible to fix.
I can replicate the same problem on the Linden Lab grid by hitting cancel immediately after a teleport starts (a teleport which would otherwise quickly succeed).
user_profiles
Justin Clark-Casey (justincc) 2013-03-12 22:16:09 +00:00
parent 0c6268fe56
commit c43d4b5572
2 changed files with 149 additions and 34 deletions

View File

@ -148,6 +148,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
protected virtual void OnNewClient(IClientAPI client) protected virtual void OnNewClient(IClientAPI client)
{ {
client.OnTeleportCancel += OnClientCancelTeleport;
client.OnTeleportHomeRequest += TeleportHome; client.OnTeleportHomeRequest += TeleportHome;
client.OnTeleportLandmarkRequest += RequestTeleportLandmark; client.OnTeleportLandmarkRequest += RequestTeleportLandmark;
} }
@ -168,6 +169,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
#region Agent Teleports #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) public void Teleport(ScenePresence sp, ulong regionHandle, Vector3 position, Vector3 lookAt, uint teleportFlags)
{ {
if (sp.Scene.Permissions.IsGridGod(sp.UUID)) if (sp.Scene.Permissions.IsGridGod(sp.UUID))
@ -567,6 +576,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
return; 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. // Past this point we have to attempt clean up if the teleport fails, so update transfer state.
m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring); m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
@ -631,7 +649,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
return; 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( m_log.DebugFormat(
"[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} from {1} to {2}", "[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} from {1} to {2}",
@ -714,14 +741,19 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
// } // }
} }
protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) /// <summary>
/// Clean up an inter-region teleport that did not complete, either because of simulator failure or cancellation.
/// </summary>
/// <remarks>
/// 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.
/// <remarks>
/// <param name='sp'> </param>
/// <param name='finalDestination'></param>
protected virtual void CleanupAbortedInterRegionTeleport(ScenePresence sp, GridRegion finalDestination)
{ {
m_entityTransferStateMachine.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.");
// Fail. Reset it back
sp.IsChildAgent = false; sp.IsChildAgent = false;
ReInstantiateScripts(sp); ReInstantiateScripts(sp);
@ -729,7 +761,20 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
// Finally, kill the agent we just created at the destination. // Finally, kill the agent we just created at the destination.
Scene.SimulationService.CloseAgent(finalDestination, sp.UUID); Scene.SimulationService.CloseAgent(finalDestination, sp.UUID);
}
/// <summary>
/// Signal that the inter-region teleport failed and perform cleanup.
/// </summary>
/// <param name='sp'></param>
/// <param name='finalDestination'></param>
/// <param name='logout'></param>
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); sp.Scene.EventManager.TriggerTeleportFail(sp.ControllingClient, logout);
} }
@ -2097,7 +2142,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
public bool IsInTransit(UUID id) public bool IsInTransit(UUID id)
{ {
return m_entityTransferStateMachine.IsInTransit(id); return m_entityTransferStateMachine.GetAgentTransferState(id) != null;
} }
protected void ReInstantiateScripts(ScenePresence sp) protected void ReInstantiateScripts(ScenePresence sp)

View File

@ -51,8 +51,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
/// This is a state machine. /// This is a state machine.
/// ///
/// [Entry] => Preparing /// [Entry] => Preparing
/// Preparing => { Transferring || CleaningUp || [Exit] } /// Preparing => { Transferring || Cancelling || CleaningUp || [Exit] }
/// Transferring => { ReceivedAtDestination || CleaningUp } /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp }
/// Cancelling => CleaningUp
/// ReceivedAtDestination => CleaningUp /// ReceivedAtDestination => CleaningUp
/// CleaningUp => [Exit] /// CleaningUp => [Exit]
/// ///
@ -64,7 +65,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
Preparing, // The agent is being prepared for transfer Preparing, // The agent is being prepared for transfer
Transferring, // The agent is in the process of being transferred to a destination 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 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.
} }
/// <summary> /// <summary>
@ -115,42 +117,110 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
/// <param name='newState'></param> /// <param name='newState'></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref='Exception'>Illegal transitions will throw an Exception</exception> /// <exception cref='Exception'>Illegal transitions will throw an Exception</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) lock (m_agentsInTransit)
{ {
// Illegal to try and update an agent that's not actually in transit. // Illegal to try and update an agent that's not actually in transit.
if (!m_agentsInTransit.ContainsKey(id)) if (!m_agentsInTransit.ContainsKey(id))
throw new Exception( {
string.Format( if (newState != AgentTransferState.Cancelling)
"Agent with ID {0} is not registered as in transit in {1}", failureMessage = string.Format(
id, m_mod.Scene.RegionInfo.RegionName)); "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 (!transitionOkay)
failureMessage
if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) = string.Format(
transitionOkay = true; "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}",
else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing) id, oldState, newState, m_mod.Scene.RegionInfo.RegionName);
transitionOkay = true; }
else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
transitionOkay = true;
if (transitionOkay) if (transitionOkay)
{
m_agentsInTransit[id] = newState; m_agentsInTransit[id] = newState;
else
throw new Exception( // m_log.DebugFormat(
string.Format( // "[ENTITY TRANSFER STATE MACHINE]: Changed agent with id {0} from state {1} to {2} in {3}",
"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.Name);
id, oldState, newState, m_mod.Scene.RegionInfo.RegionName)); }
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) /// <summary>
/// Gets the current agent transfer state.
/// </summary>
/// <returns>Null if the agent is not in transit</returns>
/// <param name='id'>
/// Identifier.
/// </param>
internal AgentTransferState? GetAgentTransferState(UUID id)
{ {
lock (m_agentsInTransit) lock (m_agentsInTransit)
return m_agentsInTransit.ContainsKey(id); {
if (!m_agentsInTransit.ContainsKey(id))
return null;
else
return m_agentsInTransit[id];
}
} }
/// <summary> /// <summary>
@ -203,14 +273,14 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
lock (m_agentsInTransit) lock (m_agentsInTransit)
{ {
if (!IsInTransit(id)) AgentTransferState? currentState = GetAgentTransferState(id);
if (currentState == null)
throw new Exception( throw new Exception(
string.Format( string.Format(
"Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit", "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)); id, m_mod.Scene.RegionInfo.RegionName));
AgentTransferState currentState = m_agentsInTransit[id];
if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination) if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination)
throw new Exception( throw new Exception(
string.Format( string.Format(