123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- /*
- * 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 || Cancelling || CleaningUp || Aborting || [Exit] }
- /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp || Aborting }
- /// Cancelling => CleaningUp || Aborting
- /// ReceivedAtDestination => CleaningUp || Aborting
- /// CleaningUp => [Exit]
- /// Aborting => [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
- Cancelling, // The user has cancelled the teleport but we have yet to act upon this.
- Aborting // The transfer is aborting. Unlike Cancelling, no compensating actions should be performed
- }
- /// <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 bool UpdateInTransit(UUID id, AgentTransferState newState)
- {
- bool transitionOkay = false;
- // We don't want to throw an exception on cancel since this can come it at any time.
- bool failIfNotOkay = true;
- // Should be a failure message if failure is not okay.
- string failureMessage = null;
- AgentTransferState? oldState = null;
- lock (m_agentsInTransit)
- {
- // Illegal to try and update an agent that's not actually in transit.
- if (!m_agentsInTransit.ContainsKey(id))
- {
- if (newState != AgentTransferState.Cancelling && newState != AgentTransferState.Aborting)
- failureMessage = string.Format(
- "Agent with ID {0} is not registered as in transit in {1}",
- id, m_mod.Scene.RegionInfo.RegionName);
- else
- failIfNotOkay = false;
- }
- else
- {
- oldState = m_agentsInTransit[id];
- if (newState == AgentTransferState.Aborting)
- {
- transitionOkay = true;
- }
- else if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
- {
- transitionOkay = true;
- }
- else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
- {
- transitionOkay = true;
- }
- else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
- {
- transitionOkay = true;
- }
- else
- {
- if (newState == AgentTransferState.Cancelling
- && (oldState == AgentTransferState.Preparing || oldState == AgentTransferState.Transferring))
- {
- transitionOkay = true;
- }
- else
- {
- failIfNotOkay = false;
- }
- }
- if (!transitionOkay)
- failureMessage
- = string.Format(
- "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}",
- id, oldState, newState, m_mod.Scene.RegionInfo.RegionName);
- }
- if (transitionOkay)
- {
- m_agentsInTransit[id] = newState;
- // m_log.DebugFormat(
- // "[ENTITY TRANSFER STATE MACHINE]: Changed agent with id {0} from state {1} to {2} in {3}",
- // id, oldState, newState, m_mod.Scene.Name);
- }
- else if (failIfNotOkay)
- {
- throw new Exception(failureMessage);
- }
- // else
- // {
- // if (oldState != null)
- // m_log.DebugFormat(
- // "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} from state {1} to {2} in {3}",
- // id, oldState, newState, m_mod.Scene.Name);
- // else
- // m_log.DebugFormat(
- // "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} to state {1} in {2} since agent not in transit",
- // id, newState, m_mod.Scene.Name);
- // }
- }
- return transitionOkay;
- }
- /// <summary>
- /// Gets the current agent transfer state.
- /// </summary>
- /// <returns>Null if the agent is not in transit</returns>
- /// <param name='id'>
- /// Identifier.
- /// </param>
- internal AgentTransferState? GetAgentTransferState(UUID id)
- {
- lock (m_agentsInTransit)
- {
- if (!m_agentsInTransit.ContainsKey(id))
- return null;
- else
- return m_agentsInTransit[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)
- {
- AgentTransferState? currentState = GetAgentTransferState(id);
- if (currentState == null)
- throw new Exception(
- string.Format(
- "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit",
- id, m_mod.Scene.RegionInfo.RegionName));
- 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 (count-- > 0)
- {
- lock (m_agentsInTransit)
- {
- if (m_agentsInTransit[id] == AgentTransferState.ReceivedAtDestination)
- break;
- }
- // 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;
- }
- }
- }
- }
|