diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs
index 7590826c56..815a3252ca 100644
--- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs
+++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs
@@ -46,35 +46,12 @@ using Nini.Config;
namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
{
- ///
- /// The possible states that an agent can be in when its being transferred between regions.
- ///
- ///
- /// This is a state machine.
- ///
- /// [Entry] => Preparing
- /// Preparing => { Transferring || CleaningUp || [Exit] }
- /// Transferring => { ReceivedAtDestination || CleaningUp }
- /// ReceivedAtDestination => CleaningUp
- /// CleaningUp => [Exit]
- ///
- /// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp
- /// However, any state can transition to CleaningUp if the teleport has failed.
- ///
- enum AgentTransferState
- {
- Preparing, // The agent is being prepared for transfer
- Transferring, // The agent is in the process of being transferred to a destination
- ReceivedAtDestination, // The destination has notified us that the agent has been successfully received
- CleaningUp // The agent is being changed to child/removed after a transfer
- }
-
public class EntityTransferModule : INonSharedRegionModule, IEntityTransferModule
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public const int DefaultMaxTransferDistance = 4095;
- public const bool EnableWaitForCallbackFromTeleportDestDefault = true;
+ public const bool WaitForAgentArrivedAtDestinationDefault = true;
///
/// The maximum distance, in standard region units (256m) that an agent is allowed to transfer.
@@ -85,13 +62,17 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
/// If true then on a teleport, the source region waits for a callback from the destination region. If
/// a callback fails to arrive within a set time then the user is pulled back into the source region.
///
- public bool EnableWaitForCallbackFromTeleportDest { get; set; }
+ public bool WaitForAgentArrivedAtDestination { get; set; }
protected bool m_Enabled = false;
- protected Scene m_scene;
+ public Scene Scene { get; private set; }
- private Dictionary m_agentsInTransit;
+ ///
+ /// Handles recording and manipulation of state for entities that are in transfer within or between regions
+ /// (cross or teleport).
+ ///
+ private EntityTransferStateMachine m_entityTransferStateMachine;
private ExpiringCache> m_bannedRegions =
new ExpiringCache>();
@@ -133,8 +114,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
IConfig transferConfig = source.Configs["EntityTransfer"];
if (transferConfig != null)
{
- EnableWaitForCallbackFromTeleportDest
- = transferConfig.GetBoolean("wait_for_callback", EnableWaitForCallbackFromTeleportDestDefault);
+ WaitForAgentArrivedAtDestination
+ = transferConfig.GetBoolean("wait_for_callback", WaitForAgentArrivedAtDestinationDefault);
MaxTransferDistance = transferConfig.GetInt("max_distance", DefaultMaxTransferDistance);
}
@@ -143,7 +124,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
MaxTransferDistance = DefaultMaxTransferDistance;
}
- m_agentsInTransit = new Dictionary();
+ m_entityTransferStateMachine = new EntityTransferStateMachine(this);
+
m_Enabled = true;
}
@@ -156,7 +138,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (!m_Enabled)
return;
- m_scene = scene;
+ Scene = scene;
scene.RegisterModuleInterface(this);
scene.EventManager.OnNewClient += OnNewClient;
@@ -177,7 +159,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (!m_Enabled)
return;
- m_eqModule = m_scene.RequestModuleInterface();
+ m_eqModule = Scene.RequestModuleInterface();
}
#endregion
@@ -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);
}
///
@@ -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
///
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;
- }
-
- ///
- /// Set that an agent is in transit.
- ///
- /// The ID of the agent being teleported
- /// true if the agent was not already in transit, false if it was
- private bool SetInTransit(UUID id)
- {
- lock (m_agentsInTransit)
- {
- if (!m_agentsInTransit.ContainsKey(id))
- {
- m_agentsInTransit[id] = AgentTransferState.Preparing;
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// Updates the state of an agent that is already in transit.
- ///
- ///
- ///
- ///
- /// Illegal transitions will throw an Exception
- private void UpdateInTransit(UUID id, AgentTransferState newState)
- {
- lock (m_agentsInTransit)
- {
- // Illegal to try and update an agent that's not actually in transit.
- if (!m_agentsInTransit.ContainsKey(id))
- throw new Exception(string.Format("Agent with ID {0} is not registered as in transit", id));
-
- AgentTransferState oldState = m_agentsInTransit[id];
-
- bool transitionOkay = false;
-
- if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
- transitionOkay = true;
- else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
- transitionOkay = true;
- else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
- transitionOkay = true;
-
- if (transitionOkay)
- m_agentsInTransit[id] = newState;
- else
- throw new Exception(
- string.Format(
- "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2}",
- id, oldState, newState));
- }
- }
-
public bool IsInTransit(UUID id)
{
- lock (m_agentsInTransit)
- return m_agentsInTransit.ContainsKey(id);
- }
-
- ///
- /// Removes an agent from the transit state machine.
- ///
- ///
- /// true if the agent was flagged as being teleported when this method was called, false otherwise
- private bool ResetFromTransit(UUID id)
- {
- lock (m_agentsInTransit)
- {
- if (m_agentsInTransit.ContainsKey(id))
- {
- AgentTransferState state = m_agentsInTransit[id];
-
- if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination)
- {
- // FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed
- // to be handled properly - ResetFromTransit() could be invoked at any step along the process
- m_log.WarnFormat(
- "[ENTITY TRANSFER MODULE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first",
- id, state, AgentTransferState.CleaningUp);
-
-// throw new Exception(
-// "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first",
-// state, AgentTransferState.CleaningUp);
- }
-
- m_agentsInTransit.Remove(id);
-
- m_log.DebugFormat(
- "[ENTITY TRANSFER MODULE]: Agent {0} cleared from transit in {1}",
- id, m_scene.RegionInfo.RegionName);
-
- return true;
- }
- }
-
- m_log.WarnFormat(
- "[ENTITY TRANSFER MODULE]: Agent {0} requested to clear from transit in {1} but was already cleared.",
- id, m_scene.RegionInfo.RegionName);
-
- return false;
+ return m_entityTransferStateMachine.IsInTransit(id);
}
protected void ReInstantiateScripts(ScenePresence sp)
diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs
new file mode 100644
index 0000000000..d0cab49563
--- /dev/null
+++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Contributors, http://opensimulator.org/
+ * See CONTRIBUTORS.TXT for a full list of copyright holders.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the OpenSimulator Project nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Reflection;
+using System.Threading;
+using OpenMetaverse;
+using log4net;
+using Nini.Config;
+using OpenSim.Framework;
+using OpenSim.Framework.Capabilities;
+using OpenSim.Framework.Client;
+using OpenSim.Region.Framework.Interfaces;
+using OpenSim.Region.Framework.Scenes;
+using OpenSim.Region.Physics.Manager;
+using OpenSim.Services.Interfaces;
+using GridRegion = OpenSim.Services.Interfaces.GridRegion;
+
+namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
+{
+ ///
+ /// The possible states that an agent can be in when its being transferred between regions.
+ ///
+ ///
+ /// This is a state machine.
+ ///
+ /// [Entry] => Preparing
+ /// Preparing => { Transferring || CleaningUp || [Exit] }
+ /// Transferring => { ReceivedAtDestination || CleaningUp }
+ /// ReceivedAtDestination => CleaningUp
+ /// CleaningUp => [Exit]
+ ///
+ /// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp
+ /// However, any state can transition to CleaningUp if the teleport has failed.
+ ///
+ enum AgentTransferState
+ {
+ Preparing, // The agent is being prepared for transfer
+ Transferring, // The agent is in the process of being transferred to a destination
+ ReceivedAtDestination, // The destination has notified us that the agent has been successfully received
+ CleaningUp // The agent is being changed to child/removed after a transfer
+ }
+
+ ///
+ /// Records the state of entities when they are in transfer within or between regions (cross or teleport).
+ ///
+ public class EntityTransferStateMachine
+ {
+ private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ ///
+ /// If true then on a teleport, the source region waits for a callback from the destination region. If
+ /// a callback fails to arrive within a set time then the user is pulled back into the source region.
+ ///
+ public bool EnableWaitForAgentArrivedAtDestination { get; set; }
+
+ private EntityTransferModule m_mod;
+
+ private Dictionary m_agentsInTransit = new Dictionary();
+
+ public EntityTransferStateMachine(EntityTransferModule module)
+ {
+ m_mod = module;
+ }
+
+ ///
+ /// Set that an agent is in transit.
+ ///
+ /// The ID of the agent being teleported
+ /// true if the agent was not already in transit, false if it was
+ internal bool SetInTransit(UUID id)
+ {
+ lock (m_agentsInTransit)
+ {
+ if (!m_agentsInTransit.ContainsKey(id))
+ {
+ m_agentsInTransit[id] = AgentTransferState.Preparing;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Updates the state of an agent that is already in transit.
+ ///
+ ///
+ ///
+ ///
+ /// Illegal transitions will throw an Exception
+ internal void UpdateInTransit(UUID id, AgentTransferState newState)
+ {
+ lock (m_agentsInTransit)
+ {
+ // Illegal to try and update an agent that's not actually in transit.
+ if (!m_agentsInTransit.ContainsKey(id))
+ throw new Exception(
+ string.Format(
+ "Agent with ID {0} is not registered as in transit in {1}",
+ id, m_mod.Scene.RegionInfo.RegionName));
+
+ AgentTransferState oldState = m_agentsInTransit[id];
+
+ bool transitionOkay = false;
+
+ if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
+ transitionOkay = true;
+ else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
+ transitionOkay = true;
+ else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
+ transitionOkay = true;
+
+ if (transitionOkay)
+ m_agentsInTransit[id] = newState;
+ else
+ throw new Exception(
+ string.Format(
+ "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}",
+ id, oldState, newState, m_mod.Scene.RegionInfo.RegionName));
+ }
+ }
+
+ internal bool IsInTransit(UUID id)
+ {
+ lock (m_agentsInTransit)
+ return m_agentsInTransit.ContainsKey(id);
+ }
+
+ ///
+ /// Removes an agent from the transit state machine.
+ ///
+ ///
+ /// true if the agent was flagged as being teleported when this method was called, false otherwise
+ internal bool ResetFromTransit(UUID id)
+ {
+ lock (m_agentsInTransit)
+ {
+ if (m_agentsInTransit.ContainsKey(id))
+ {
+ AgentTransferState state = m_agentsInTransit[id];
+
+ if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination)
+ {
+ // FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed
+ // to be handled properly - ResetFromTransit() could be invoked at any step along the process
+ m_log.WarnFormat(
+ "[ENTITY TRANSFER STATE MACHINE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first in {3}",
+ id, state, AgentTransferState.CleaningUp, m_mod.Scene.RegionInfo.RegionName);
+
+// throw new Exception(
+// "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first",
+// state, AgentTransferState.CleaningUp);
+ }
+
+ m_agentsInTransit.Remove(id);
+
+ m_log.DebugFormat(
+ "[ENTITY TRANSFER STATE MACHINE]: Agent {0} cleared from transit in {1}",
+ id, m_mod.Scene.RegionInfo.RegionName);
+
+ return true;
+ }
+ }
+
+ m_log.WarnFormat(
+ "[ENTITY TRANSFER STATE MACHINE]: Agent {0} requested to clear from transit in {1} but was already cleared",
+ id, m_mod.Scene.RegionInfo.RegionName);
+
+ return false;
+ }
+
+ internal bool WaitForAgentArrivedAtDestination(UUID id)
+ {
+ if (!m_mod.WaitForAgentArrivedAtDestination)
+ return true;
+
+ lock (m_agentsInTransit)
+ {
+ if (!IsInTransit(id))
+ throw new Exception(
+ string.Format(
+ "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit",
+ id, m_mod.Scene.RegionInfo.RegionName));
+
+ AgentTransferState currentState = m_agentsInTransit[id];
+
+ if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination)
+ throw new Exception(
+ string.Format(
+ "Asked to wait for destination callback for agent with ID {0} in {1} but agent is in state {2}",
+ id, m_mod.Scene.RegionInfo.RegionName, currentState));
+ }
+
+ int count = 200;
+
+ // There should be no race condition here since no other code should be removing the agent transfer or
+ // changing the state to another other than Transferring => ReceivedAtDestination.
+ while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0)
+ {
+// m_log.Debug(" >>> Waiting... " + count);
+ Thread.Sleep(100);
+ }
+
+ return count > 0;
+ }
+
+ internal void SetAgentArrivedAtDestination(UUID id)
+ {
+ lock (m_agentsInTransit)
+ {
+ if (!m_agentsInTransit.ContainsKey(id))
+ {
+ m_log.WarnFormat(
+ "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but no teleport request is active",
+ m_mod.Scene.RegionInfo.RegionName, id);
+
+ return;
+ }
+
+ AgentTransferState currentState = m_agentsInTransit[id];
+
+ if (currentState == AgentTransferState.ReceivedAtDestination)
+ {
+ // An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification.
+ m_log.WarnFormat(
+ "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but notification has already previously been received",
+ m_mod.Scene.RegionInfo.RegionName, id);
+ }
+ else if (currentState != AgentTransferState.Transferring)
+ {
+ m_log.ErrorFormat(
+ "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but agent is in state {2}",
+ m_mod.Scene.RegionInfo.RegionName, id, currentState);
+
+ return;
+ }
+
+ m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs
index f9ab734dcc..5be5153365 100644
--- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs
+++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/HGEntityTransferModule.cs
@@ -117,7 +117,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
protected override GridRegion GetFinalDestination(GridRegion region)
{
- int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, region.RegionID);
+ int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, region.RegionID);
m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: region {0} flags: {1}", region.RegionID, flags);
if ((flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
@@ -139,7 +139,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (base.NeedsClosing(drawdist, oldRegionX, newRegionX, oldRegionY, newRegionY, reg))
return true;
- int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, reg.RegionID);
+ int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, reg.RegionID);
if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
return true;
@@ -152,7 +152,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
if (logout)
{
// Log them out of this grid
- m_scene.PresenceService.LogoutAgent(sp.ControllingClient.SessionId);
+ Scene.PresenceService.LogoutAgent(sp.ControllingClient.SessionId);
}
}
@@ -161,7 +161,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
m_log.DebugFormat("[HG ENTITY TRANSFER MODULE]: CreateAgent {0} {1}", reg.ServerURI, finalDestination.ServerURI);
reason = string.Empty;
logout = false;
- int flags = m_scene.GridService.GetRegionFlags(m_scene.RegionInfo.ScopeID, reg.RegionID);
+ int flags = Scene.GridService.GetRegionFlags(Scene.RegionInfo.ScopeID, reg.RegionID);
if (flags == -1 /* no region in DB */ || (flags & (int)OpenSim.Data.RegionFlags.Hyperlink) != 0)
{
// this user is going to another grid
@@ -201,7 +201,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
"[ENTITY TRANSFER MODULE]: Request to teleport {0} {1} home", client.Name, client.AgentId);
// Let's find out if this is a foreign user or a local user
- IUserManagement uMan = m_scene.RequestModuleInterface();
+ IUserManagement uMan = Scene.RequestModuleInterface();
if (uMan != null && uMan.IsLocalGridUser(id))
{
// local grid user
@@ -265,7 +265,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
return;
}
- GridRegion info = m_scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
+ GridRegion info = Scene.GridService.GetRegionByUUID(UUID.Zero, lm.RegionID);
// Local region?
if (info != null)
@@ -335,8 +335,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();
- UserAccount account = m_scene.UserAccountService.GetUserAccount(m_scene.RegionInfo.ScopeID, obj.AgentId);
+ IUserManagement uMan = Scene.RequestModuleInterface();
+// UserAccount account = Scene.UserAccountService.GetUserAccount(Scene.RegionInfo.ScopeID, obj.AgentId);
+
if (uMan != null && uMan.IsLocalGridUser(obj.AgentId))
{
// local grid user