refactor: factor out entity transfer state machine into a separate class to make code more analyzable

0.7.3-extended
Justin Clark-Casey (justincc) 2012-05-28 23:06:00 +01:00
parent dca1ca1d07
commit 533d1ea20c
3 changed files with 331 additions and 239 deletions

View File

@ -46,35 +46,12 @@ using Nini.Config;
namespace OpenSim.Region.CoreModules.Framework.EntityTransfer namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
{ {
/// <summary>
/// The possible states that an agent can be in when its being transferred between regions.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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 public class EntityTransferModule : INonSharedRegionModule, IEntityTransferModule
{ {
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public const int DefaultMaxTransferDistance = 4095; public const int DefaultMaxTransferDistance = 4095;
public const bool EnableWaitForCallbackFromTeleportDestDefault = true; public const bool WaitForAgentArrivedAtDestinationDefault = true;
/// <summary> /// <summary>
/// The maximum distance, in standard region units (256m) that an agent is allowed to transfer. /// 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 /// 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. /// a callback fails to arrive within a set time then the user is pulled back into the source region.
/// </summary> /// </summary>
public bool EnableWaitForCallbackFromTeleportDest { get; set; } public bool WaitForAgentArrivedAtDestination { get; set; }
protected bool m_Enabled = false; protected bool m_Enabled = false;
protected Scene m_scene; public Scene Scene { get; private set; }
private Dictionary<UUID, AgentTransferState> m_agentsInTransit; /// <summary>
/// Handles recording and manipulation of state for entities that are in transfer within or between regions
/// (cross or teleport).
/// </summary>
private EntityTransferStateMachine m_entityTransferStateMachine;
private ExpiringCache<UUID, ExpiringCache<ulong, DateTime>> m_bannedRegions = private ExpiringCache<UUID, ExpiringCache<ulong, DateTime>> m_bannedRegions =
new ExpiringCache<UUID, ExpiringCache<ulong, DateTime>>(); new ExpiringCache<UUID, ExpiringCache<ulong, DateTime>>();
@ -133,8 +114,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
IConfig transferConfig = source.Configs["EntityTransfer"]; IConfig transferConfig = source.Configs["EntityTransfer"];
if (transferConfig != null) if (transferConfig != null)
{ {
EnableWaitForCallbackFromTeleportDest WaitForAgentArrivedAtDestination
= transferConfig.GetBoolean("wait_for_callback", EnableWaitForCallbackFromTeleportDestDefault); = transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault);
MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance); MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance);
} }
@ -143,7 +124,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
MaxTransferDistance = DefaultMaxTransferDistance; MaxTransferDistance = DefaultMaxTransferDistance;
} }
m_agentsInTransit = new Dictionary<UUID, AgentTransferState>(); m_entityTransferStateMachine = new EntityTransferStateMachine(this);
m_Enabled = true; m_Enabled = true;
} }
@ -156,7 +138,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (!m_Enabled) if (!m_Enabled)
return; return;
m_scene = scene; Scene = scene;
scene.RegisterModuleInterface<IEntityTransferModule>(this); scene.RegisterModuleInterface<IEntityTransferModule>(this);
scene.EventManager.OnNewClient += OnNewClient; scene.EventManager.OnNewClient += OnNewClient;
@ -177,7 +159,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (!m_Enabled) if (!m_Enabled)
return; return;
m_eqModule = m_scene.RequestModuleInterface<IEventQueue>(); m_eqModule = Scene.RequestModuleInterface<IEventQueue>();
} }
#endregion #endregion
@ -220,7 +202,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
e.Message, e.StackTrace); e.Message, e.StackTrace);
// Make sure that we clear the in-transit flag so that future teleport attempts don't always fail. // 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"); sp.ControllingClient.SendTeleportFailed("Internal error");
} }
@ -239,7 +221,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
"[ENTITY TRANSFER MODULE]: Teleport for {0} to {1} within {2}", "[ENTITY TRANSFER MODULE]: Teleport for {0} to {1} within {2}",
sp.Name, position, sp.Scene.RegionInfo.RegionName); sp.Name, position, sp.Scene.RegionInfo.RegionName);
if (!SetInTransit(sp.UUID)) if (!m_entityTransferStateMachine.SetInTransit(sp.UUID))
{ {
m_log.DebugFormat( m_log.DebugFormat(
"[ENTITY TRANSFER MODULE]: Ignoring within region teleport request of {0} {1} to {2} - agent is already in transit.", "[ENTITY TRANSFER MODULE]: Ignoring within region teleport request of {0} {1} to {2} - agent is already in transit.",
@ -276,7 +258,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
position.Z = newPosZ; position.Z = newPosZ;
} }
UpdateInTransit(sp.UUID, AgentTransferState.Transferring); m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
sp.ControllingClient.SendTeleportStart(teleportFlags); sp.ControllingClient.SendTeleportStart(teleportFlags);
@ -284,15 +266,15 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
sp.Velocity = Vector3.Zero; sp.Velocity = Vector3.Zero;
sp.Teleport(position); sp.Teleport(position);
UpdateInTransit(sp.UUID, AgentTransferState.ReceivedAtDestination); m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.ReceivedAtDestination);
foreach (SceneObjectGroup grp in sp.GetAttachments()) foreach (SceneObjectGroup grp in sp.GetAttachments())
{ {
sp.Scene.EventManager.TriggerOnScriptChangedEvent(grp.LocalId, (uint)Changed.TELEPORT); sp.Scene.EventManager.TriggerOnScriptChangedEvent(grp.LocalId, (uint)Changed.TELEPORT);
} }
UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
} }
/// <summary> /// <summary>
@ -310,7 +292,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
{ {
uint x = 0, y = 0; uint x = 0, y = 0;
Utils.LongToUInts(regionHandle, out x, out y); 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) if (reg != null)
{ {
@ -392,7 +374,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 // 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. // of whether the destination region completes the teleport.
if (!SetInTransit(sp.UUID)) if (!m_entityTransferStateMachine.SetInTransit(sp.UUID))
{ {
m_log.DebugFormat( m_log.DebugFormat(
"[ENTITY TRANSFER MODULE]: Ignoring teleport request of {0} {1} to {2} ({3}) {4}/{5} - agent is already in transit.", "[ENTITY TRANSFER MODULE]: Ignoring teleport request of {0} {1} to {2} ({3}) {4}/{5} - agent is already in transit.",
@ -404,7 +386,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (reg == null || finalDestination == null) if (reg == null || finalDestination == null)
{ {
sp.ControllingClient.SendTeleportFailed("Unable to locate destination"); sp.ControllingClient.SendTeleportFailed("Unable to locate destination");
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
return; return;
} }
@ -425,7 +407,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY, sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY,
MaxTransferDistance)); MaxTransferDistance));
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
return; return;
} }
@ -445,7 +427,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (endPoint.Address == null) if (endPoint.Address == null)
{ {
sp.ControllingClient.SendTeleportFailed("Remote Region appears to be down"); sp.ControllingClient.SendTeleportFailed("Remote Region appears to be down");
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
return; return;
} }
@ -463,11 +445,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
string reason; string reason;
string version; string version;
if (!m_scene.SimulationService.QueryAccess( if (!Scene.SimulationService.QueryAccess(
finalDestination, sp.ControllingClient.AgentId, Vector3.Zero, out version, out reason)) finalDestination, sp.ControllingClient.AgentId, Vector3.Zero, out version, out reason))
{ {
sp.ControllingClient.SendTeleportFailed(reason); sp.ControllingClient.SendTeleportFailed(reason);
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
m_log.DebugFormat( m_log.DebugFormat(
"[ENTITY TRANSFER MODULE]: {0} was stopped from teleporting from {1} to {2} because {3}", "[ENTITY TRANSFER MODULE]: {0} was stopped from teleporting from {1} to {2} because {3}",
@ -521,7 +503,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout))
{ {
sp.ControllingClient.SendTeleportFailed(String.Format("Teleport refused: {0}", reason)); sp.ControllingClient.SendTeleportFailed(String.Format("Teleport refused: {0}", reason));
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
m_log.DebugFormat( m_log.DebugFormat(
"[ENTITY TRANSFER MODULE]: Teleport of {0} from {1} to {2} was refused because {3}", "[ENTITY TRANSFER MODULE]: Teleport of {0} from {1} to {2} was refused because {3}",
@ -531,7 +513,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
} }
// 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.
UpdateInTransit(sp.UUID, AgentTransferState.Transferring); m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.Transferring);
// OK, it got this agent. Let's close some child agents // OK, it got this agent. Let's close some child agents
sp.CloseChildAgents(newRegionX, newRegionY); sp.CloseChildAgents(newRegionX, newRegionY);
@ -613,7 +595,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
// TeleportFinish makes the client send CompleteMovementIntoRegion (at the destination), which // 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 // 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. // 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( 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.", "[ENTITY TRANSFER MODULE]: Teleport of {0} to {1} from {2} failed due to no callback from destination region. Returning avatar to source region.",
@ -623,7 +605,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
return; return;
} }
UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp); m_entityTransferStateMachine.UpdateInTransit(sp.UUID, AgentTransferState.CleaningUp);
// For backwards compatibility // For backwards compatibility
if (version == "Unknown" || version == string.Empty) if (version == "Unknown" || version == string.Empty)
@ -673,12 +655,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
sp.UUID); sp.UUID);
} }
ResetFromTransit(sp.UUID); m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
} }
protected virtual void Fail(ScenePresence sp, GridRegion finalDestination, bool logout) 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 // Client never contacted destination. Let's restore everything back
sp.ControllingClient.SendTeleportFailed("Problems connecting to destination."); sp.ControllingClient.SendTeleportFailed("Problems connecting to destination.");
@ -690,17 +672,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
EnableChildAgents(sp); EnableChildAgents(sp);
// Finally, kill the agent we just created at the destination. // 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); 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) protected virtual bool CreateAgent(ScenePresence sp, GridRegion reg, GridRegion finalDestination, AgentCircuitData agentCircuit, uint teleportFlags, out string reason, out bool logout)
{ {
logout = false; 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) if (success)
sp.Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout); sp.Scene.EventManager.TriggerTeleportStart(sp.ControllingClient, reg, finalDestination, teleportFlags, logout);
@ -710,7 +692,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
protected virtual bool UpdateAgent(GridRegion reg, GridRegion finalDestination, AgentData agent) 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) protected virtual void SetCallbackURL(AgentData agent, RegionInfo region)
@ -773,7 +755,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
/// <param name="position"></param> /// <param name="position"></param>
public virtual void RequestTeleportLandmark(IClientAPI remoteClient, AssetLandmark lm) 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) if (info == null)
{ {
@ -794,12 +776,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
m_log.DebugFormat( m_log.DebugFormat(
"[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId); "[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId);
//OpenSim.Services.Interfaces.PresenceInfo pinfo = m_scene.PresenceService.GetAgent(client.SessionId); //OpenSim.Services.Interfaces.PresenceInfo pinfo = Scene.PresenceService.GetAgent(client.SessionId);
GridUserInfo uinfo = m_scene.GridUserService.GetGridUserInfo(client.AgentId.ToString()); GridUserInfo uinfo = Scene.GridUserService.GetGridUserInfo(client.AgentId.ToString());
if (uinfo != null) 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) if (regionInfo == null)
{ {
// can't find the Home region: Tell viewer and abort // can't find the Home region: Tell viewer and abort
@ -1108,7 +1090,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
try try
{ {
SetInTransit(agent.UUID); m_entityTransferStateMachine.SetInTransit(agent.UUID);
ulong neighbourHandle = Utils.UIntsToLong((uint)(neighbourx * Constants.RegionSize), (uint)(neighboury * Constants.RegionSize)); ulong neighbourHandle = Utils.UIntsToLong((uint)(neighbourx * Constants.RegionSize), (uint)(neighboury * Constants.RegionSize));
@ -1138,16 +1120,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
cAgent.CallbackURI = String.Empty; cAgent.CallbackURI = String.Empty;
// Beyond this point, extra cleanup is needed beyond removing transit state // 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)) if (!m_scene.SimulationService.UpdateAgent(neighbourRegion, cAgent))
{ {
// region doesn't take it // region doesn't take it
UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp); m_entityTransferStateMachine.UpdateInTransit(agent.UUID, AgentTransferState.CleaningUp);
ReInstantiateScripts(agent); ReInstantiateScripts(agent);
agent.AddToPhysicalScene(isFlying); agent.AddToPhysicalScene(isFlying);
ResetFromTransit(agent.UUID); m_entityTransferStateMachine.ResetFromTransit(agent.UUID);
return agent; return agent;
} }
@ -1185,16 +1167,16 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
} }
// SUCCESS! // 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. // 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(); agent.MakeChildAgent();
// FIXME: Possibly this should occur lower down after other commands to close other agents, // FIXME: Possibly this should occur lower down after other commands to close other agents,
// but not sure yet what the side effects would be. // 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 // now we have a child agent in this region. Request all interesting data about other (root) agents
agent.SendOtherAgentsAvatarDataToMe(); agent.SendOtherAgentsAvatarDataToMe();
@ -1669,41 +1651,11 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
#endregion #endregion
#region Agent Arrived #region Agent Arrived
public void AgentArrivedAtDestination(UUID id) public void AgentArrivedAtDestination(UUID id)
{ {
lock (m_agentsInTransit) m_entityTransferStateMachine.SetAgentArrivedAtDestination(id);
{
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;
}
} }
#endregion #endregion
@ -1973,8 +1925,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
//// And the new channel... //// And the new channel...
//if (m_interregionCommsOut != null) //if (m_interregionCommsOut != null)
// successYN = m_interregionCommsOut.SendCreateObject(newRegionHandle, grp, true); // successYN = m_interregionCommsOut.SendCreateObject(newRegionHandle, grp, true);
if (m_scene.SimulationService != null) if (Scene.SimulationService != null)
successYN = m_scene.SimulationService.CreateObject(destination, newPosition, grp, true); successYN = Scene.SimulationService.CreateObject(destination, newPosition, grp, true);
if (successYN) if (successYN)
{ {
@ -2045,139 +1997,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
#region Misc #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;
}
/// <summary>
/// Set that an agent is in transit.
/// </summary>
/// <param name='id'>The ID of the agent being teleported</param>
/// <returns>true if the agent was not already in transit, false if it was</returns>
private bool SetInTransit(UUID id)
{
lock (m_agentsInTransit)
{
if (!m_agentsInTransit.ContainsKey(id))
{
m_agentsInTransit[id] = AgentTransferState.Preparing;
return true;
}
}
return false;
}
/// <summary>
/// Updates the state of an agent that is already in transit.
/// </summary>
/// <param name='id'></param>
/// <param name='newState'></param>
/// <returns></returns>
/// <exception cref='Exception'>Illegal transitions will throw an Exception</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) public bool IsInTransit(UUID id)
{ {
lock (m_agentsInTransit) return m_entityTransferStateMachine.IsInTransit(id);
return m_agentsInTransit.ContainsKey(id);
}
/// <summary>
/// Removes an agent from the transit state machine.
/// </summary>
/// <param name='id'></param>
/// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns>
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;
} }
protected void ReInstantiateScripts(ScenePresence sp) protected void ReInstantiateScripts(ScenePresence sp)

View File

@ -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
{
/// <summary>
/// The possible states that an agent can be in when its being transferred between regions.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
}
/// <summary>
/// Records the state of entities when they are in transfer within or between regions (cross or teleport).
/// </summary>
public class EntityTransferStateMachine
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// 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.
/// </summary>
public bool EnableWaitForAgentArrivedAtDestination { get; set; }
private EntityTransferModule m_mod;
private Dictionary<UUID, AgentTransferState> m_agentsInTransit = new Dictionary<UUID, AgentTransferState>();
public EntityTransferStateMachine(EntityTransferModule module)
{
m_mod = module;
}
/// <summary>
/// Set that an agent is in transit.
/// </summary>
/// <param name='id'>The ID of the agent being teleported</param>
/// <returns>true if the agent was not already in transit, false if it was</returns>
internal bool SetInTransit(UUID id)
{
lock (m_agentsInTransit)
{
if (!m_agentsInTransit.ContainsKey(id))
{
m_agentsInTransit[id] = AgentTransferState.Preparing;
return true;
}
}
return false;
}
/// <summary>
/// Updates the state of an agent that is already in transit.
/// </summary>
/// <param name='id'></param>
/// <param name='newState'></param>
/// <returns></returns>
/// <exception cref='Exception'>Illegal transitions will throw an Exception</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);
}
/// <summary>
/// Removes an agent from the transit state machine.
/// </summary>
/// <param name='id'></param>
/// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns>
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;
}
}
}
}

View File

@ -117,7 +117,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
protected override GridRegion GetFinalDestination(GridRegion region) 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); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: region {0} flags: {1}", region.RegionID, flags);
if ((flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0) 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)) if (base.NeedsClosing(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY, reg))
return true; 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) if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
return true; return true;
@ -152,7 +152,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (logout) if (logout)
{ {
// Log them out of this grid // 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); m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: CreateAgent {0} {1}", reg.ServerURI, finalDestination.ServerURI);
reason = string.Empty; reason = string.Empty;
logout = false; 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) if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
{ {
// this user is going to another grid // 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); "[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 // Let's find out if this is a foreign user or a local user
IUserManagement uMan = m_scene.RequestModuleInterface<IUserManagement>(); IUserManagement uMan = Scene.RequestModuleInterface<IUserManagement>();
if (uMan != null && uMan.IsLocalGridUser(id)) if (uMan != null && uMan.IsLocalGridUser(id))
{ {
// local grid user // local grid user
@ -265,7 +265,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
return; return;
} }
GridRegion info = m_scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID); GridRegion info = Scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
// Local region? // Local region?
if (info != null) if (info != null)
@ -335,8 +335,9 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
} }
// Let's find out if this is a foreign user or a local user // Let's find out if this is a foreign user or a local user
IUserManagement uMan = m_scene.RequestModuleInterface<IUserManagement>(); IUserManagement uMan = Scene.RequestModuleInterface<IUserManagement>();
UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, obj.AgentId); // UserAccount account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, obj.AgentId);
if (uMan != null && uMan.IsLocalGridUser(obj.AgentId)) if (uMan != null && uMan.IsLocalGridUser(obj.AgentId))
{ {
// local grid user // local grid user