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
{
/// <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
{
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;
/// <summary>
/// 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.
/// </summary>
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<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 =
new ExpiringCache<UUID, ExpiringCache<ulong, DateTime>>();
@ -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<UUID, AgentTransferState>();
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<IEntityTransferModule>(this);
scene.EventManager.OnNewClient += OnNewClient;
@ -177,7 +159,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (!m_Enabled)
return;
m_eqModule = m_scene.RequestModuleInterface<IEventQueue>();
m_eqModule = Scene.RequestModuleInterface<IEventQueue>();
}
#endregion
@ -220,7 +202,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");
}
@ -239,7 +221,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.",
@ -276,7 +258,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);
@ -284,15 +266,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);
}
/// <summary>
@ -310,7 +292,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)
{
@ -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
// 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.",
@ -404,7 +386,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;
}
@ -425,7 +407,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
sourceRegion.RegionName, sourceRegion.RegionLocX, sourceRegion.RegionLocY,
MaxTransferDistance));
ResetFromTransit(sp.UUID);
m_entityTransferStateMachine.ResetFromTransit(sp.UUID);
return;
}
@ -445,7 +427,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;
}
@ -463,11 +445,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}",
@ -521,7 +503,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}",
@ -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.
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);
@ -613,7 +595,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.",
@ -623,7 +605,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)
@ -673,12 +655,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.");
@ -690,17 +672,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);
@ -710,7 +692,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)
@ -773,7 +755,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
/// <param name="position"></param>
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)
{
@ -794,12 +776,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
@ -1108,7 +1090,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));
@ -1138,16 +1120,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;
}
@ -1185,16 +1167,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();
@ -1669,41 +1651,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
@ -1973,8 +1925,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)
{
@ -2045,139 +1997,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;
}
/// <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)
{
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>
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)

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)
{
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>();
IUserManagement uMan = Scene.RequestModuleInterface<IUserManagement>();
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,9 @@ 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<IUserManagement>();
UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, obj.AgentId);
IUserManagement uMan = Scene.RequestModuleInterface<IUserManagement>();
// UserAccount account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, obj.AgentId);
if (uMan != null && uMan.IsLocalGridUser(obj.AgentId))
{
// local grid user