EntityTransferStateMachine.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Net;
  30. using System.Reflection;
  31. using System.Threading;
  32. using OpenMetaverse;
  33. using log4net;
  34. using Nini.Config;
  35. using OpenSim.Framework;
  36. using OpenSim.Framework.Capabilities;
  37. using OpenSim.Framework.Client;
  38. using OpenSim.Region.Framework.Interfaces;
  39. using OpenSim.Region.Framework.Scenes;
  40. using OpenSim.Region.Physics.Manager;
  41. using OpenSim.Services.Interfaces;
  42. using GridRegion = OpenSim.Services.Interfaces.GridRegion;
  43. namespace OpenSim.Region.CoreModules.Framework.EntityTransfer
  44. {
  45. /// <summary>
  46. /// The possible states that an agent can be in when its being transferred between regions.
  47. /// </summary>
  48. /// <remarks>
  49. /// This is a state machine.
  50. ///
  51. /// [Entry] => Preparing
  52. /// Preparing => { Transferring || Cancelling || CleaningUp || Aborting || [Exit] }
  53. /// Transferring => { ReceivedAtDestination || Cancelling || CleaningUp || Aborting }
  54. /// Cancelling => CleaningUp || Aborting
  55. /// ReceivedAtDestination => CleaningUp || Aborting
  56. /// CleaningUp => [Exit]
  57. /// Aborting => [Exit]
  58. ///
  59. /// In other words, agents normally travel throwing Preparing => Transferring => ReceivedAtDestination => CleaningUp
  60. /// However, any state can transition to CleaningUp if the teleport has failed.
  61. /// </remarks>
  62. enum AgentTransferState
  63. {
  64. Preparing, // The agent is being prepared for transfer
  65. Transferring, // The agent is in the process of being transferred to a destination
  66. ReceivedAtDestination, // The destination has notified us that the agent has been successfully received
  67. CleaningUp, // The agent is being changed to child/removed after a transfer
  68. Cancelling, // The user has cancelled the teleport but we have yet to act upon this.
  69. Aborting // The transfer is aborting. Unlike Cancelling, no compensating actions should be performed
  70. }
  71. /// <summary>
  72. /// Records the state of entities when they are in transfer within or between regions (cross or teleport).
  73. /// </summary>
  74. public class EntityTransferStateMachine
  75. {
  76. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  77. /// <summary>
  78. /// If true then on a teleport, the source region waits for a callback from the destination region. If
  79. /// a callback fails to arrive within a set time then the user is pulled back into the source region.
  80. /// </summary>
  81. public bool EnableWaitForAgentArrivedAtDestination { get; set; }
  82. private EntityTransferModule m_mod;
  83. private Dictionary<UUID, AgentTransferState> m_agentsInTransit = new Dictionary<UUID, AgentTransferState>();
  84. public EntityTransferStateMachine(EntityTransferModule module)
  85. {
  86. m_mod = module;
  87. }
  88. /// <summary>
  89. /// Set that an agent is in transit.
  90. /// </summary>
  91. /// <param name='id'>The ID of the agent being teleported</param>
  92. /// <returns>true if the agent was not already in transit, false if it was</returns>
  93. internal bool SetInTransit(UUID id)
  94. {
  95. lock (m_agentsInTransit)
  96. {
  97. if (!m_agentsInTransit.ContainsKey(id))
  98. {
  99. m_agentsInTransit[id] = AgentTransferState.Preparing;
  100. return true;
  101. }
  102. }
  103. return false;
  104. }
  105. /// <summary>
  106. /// Updates the state of an agent that is already in transit.
  107. /// </summary>
  108. /// <param name='id'></param>
  109. /// <param name='newState'></param>
  110. /// <returns></returns>
  111. /// <exception cref='Exception'>Illegal transitions will throw an Exception</exception>
  112. internal bool UpdateInTransit(UUID id, AgentTransferState newState)
  113. {
  114. bool transitionOkay = false;
  115. // We don't want to throw an exception on cancel since this can come it at any time.
  116. bool failIfNotOkay = true;
  117. // Should be a failure message if failure is not okay.
  118. string failureMessage = null;
  119. AgentTransferState? oldState = null;
  120. lock (m_agentsInTransit)
  121. {
  122. // Illegal to try and update an agent that's not actually in transit.
  123. if (!m_agentsInTransit.ContainsKey(id))
  124. {
  125. if (newState != AgentTransferState.Cancelling && newState != AgentTransferState.Aborting)
  126. failureMessage = string.Format(
  127. "Agent with ID {0} is not registered as in transit in {1}",
  128. id, m_mod.Scene.RegionInfo.RegionName);
  129. else
  130. failIfNotOkay = false;
  131. }
  132. else
  133. {
  134. oldState = m_agentsInTransit[id];
  135. if (newState == AgentTransferState.Aborting)
  136. {
  137. transitionOkay = true;
  138. }
  139. else if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
  140. {
  141. transitionOkay = true;
  142. }
  143. else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
  144. {
  145. transitionOkay = true;
  146. }
  147. else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
  148. {
  149. transitionOkay = true;
  150. }
  151. else
  152. {
  153. if (newState == AgentTransferState.Cancelling
  154. && (oldState == AgentTransferState.Preparing || oldState == AgentTransferState.Transferring))
  155. {
  156. transitionOkay = true;
  157. }
  158. else
  159. {
  160. failIfNotOkay = false;
  161. }
  162. }
  163. if (!transitionOkay)
  164. failureMessage
  165. = string.Format(
  166. "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}",
  167. id, oldState, newState, m_mod.Scene.RegionInfo.RegionName);
  168. }
  169. if (transitionOkay)
  170. {
  171. m_agentsInTransit[id] = newState;
  172. // m_log.DebugFormat(
  173. // "[ENTITY TRANSFER STATE MACHINE]: Changed agent with id {0} from state {1} to {2} in {3}",
  174. // id, oldState, newState, m_mod.Scene.Name);
  175. }
  176. else if (failIfNotOkay)
  177. {
  178. throw new Exception(failureMessage);
  179. }
  180. // else
  181. // {
  182. // if (oldState != null)
  183. // m_log.DebugFormat(
  184. // "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} from state {1} to {2} in {3}",
  185. // id, oldState, newState, m_mod.Scene.Name);
  186. // else
  187. // m_log.DebugFormat(
  188. // "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} to state {1} in {2} since agent not in transit",
  189. // id, newState, m_mod.Scene.Name);
  190. // }
  191. }
  192. return transitionOkay;
  193. }
  194. /// <summary>
  195. /// Gets the current agent transfer state.
  196. /// </summary>
  197. /// <returns>Null if the agent is not in transit</returns>
  198. /// <param name='id'>
  199. /// Identifier.
  200. /// </param>
  201. internal AgentTransferState? GetAgentTransferState(UUID id)
  202. {
  203. lock (m_agentsInTransit)
  204. {
  205. if (!m_agentsInTransit.ContainsKey(id))
  206. return null;
  207. else
  208. return m_agentsInTransit[id];
  209. }
  210. }
  211. /// <summary>
  212. /// Removes an agent from the transit state machine.
  213. /// </summary>
  214. /// <param name='id'></param>
  215. /// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns>
  216. internal bool ResetFromTransit(UUID id)
  217. {
  218. lock (m_agentsInTransit)
  219. {
  220. if (m_agentsInTransit.ContainsKey(id))
  221. {
  222. AgentTransferState state = m_agentsInTransit[id];
  223. if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination)
  224. {
  225. // FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed
  226. // to be handled properly - ResetFromTransit() could be invoked at any step along the process
  227. m_log.WarnFormat(
  228. "[ENTITY TRANSFER STATE MACHINE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first in {3}",
  229. id, state, AgentTransferState.CleaningUp, m_mod.Scene.RegionInfo.RegionName);
  230. // throw new Exception(
  231. // "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first",
  232. // state, AgentTransferState.CleaningUp);
  233. }
  234. m_agentsInTransit.Remove(id);
  235. m_log.DebugFormat(
  236. "[ENTITY TRANSFER STATE MACHINE]: Agent {0} cleared from transit in {1}",
  237. id, m_mod.Scene.RegionInfo.RegionName);
  238. return true;
  239. }
  240. }
  241. m_log.WarnFormat(
  242. "[ENTITY TRANSFER STATE MACHINE]: Agent {0} requested to clear from transit in {1} but was already cleared",
  243. id, m_mod.Scene.RegionInfo.RegionName);
  244. return false;
  245. }
  246. internal bool WaitForAgentArrivedAtDestination(UUID id)
  247. {
  248. if (!m_mod.WaitForAgentArrivedAtDestination)
  249. return true;
  250. lock (m_agentsInTransit)
  251. {
  252. AgentTransferState? currentState = GetAgentTransferState(id);
  253. if (currentState == null)
  254. throw new Exception(
  255. string.Format(
  256. "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit",
  257. id, m_mod.Scene.RegionInfo.RegionName));
  258. if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination)
  259. throw new Exception(
  260. string.Format(
  261. "Asked to wait for destination callback for agent with ID {0} in {1} but agent is in state {2}",
  262. id, m_mod.Scene.RegionInfo.RegionName, currentState));
  263. }
  264. int count = 200;
  265. // There should be no race condition here since no other code should be removing the agent transfer or
  266. // changing the state to another other than Transferring => ReceivedAtDestination.
  267. while (count-- > 0)
  268. {
  269. lock (m_agentsInTransit)
  270. {
  271. if (m_agentsInTransit[id] == AgentTransferState.ReceivedAtDestination)
  272. break;
  273. }
  274. // m_log.Debug(" >>> Waiting... " + count);
  275. Thread.Sleep(100);
  276. }
  277. return count > 0;
  278. }
  279. internal void SetAgentArrivedAtDestination(UUID id)
  280. {
  281. lock (m_agentsInTransit)
  282. {
  283. if (!m_agentsInTransit.ContainsKey(id))
  284. {
  285. m_log.WarnFormat(
  286. "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but no teleport request is active",
  287. m_mod.Scene.RegionInfo.RegionName, id);
  288. return;
  289. }
  290. AgentTransferState currentState = m_agentsInTransit[id];
  291. if (currentState == AgentTransferState.ReceivedAtDestination)
  292. {
  293. // An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification.
  294. m_log.WarnFormat(
  295. "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but notification has already previously been received",
  296. m_mod.Scene.RegionInfo.RegionName, id);
  297. }
  298. else if (currentState != AgentTransferState.Transferring)
  299. {
  300. m_log.ErrorFormat(
  301. "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but agent is in state {2}",
  302. m_mod.Scene.RegionInfo.RegionName, id, currentState);
  303. return;
  304. }
  305. m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination;
  306. }
  307. }
  308. }
  309. }