EntityTransferStateMachine.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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.PhysicsModules.SharedBase;
  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. private static readonly string LogHeader = "[ENTITY TRANSFER STATE MACHINE]";
  78. /// <summary>
  79. /// If true then on a teleport, the source region waits for a callback from the destination region. If
  80. /// a callback fails to arrive within a set time then the user is pulled back into the source region.
  81. /// </summary>
  82. public bool EnableWaitForAgentArrivedAtDestination { get; set; }
  83. private EntityTransferModule m_mod;
  84. private Dictionary<UUID, AgentTransferState> m_agentsInTransit = new Dictionary<UUID, AgentTransferState>();
  85. public EntityTransferStateMachine(EntityTransferModule module)
  86. {
  87. m_mod = module;
  88. }
  89. /// <summary>
  90. /// Set that an agent is in transit.
  91. /// </summary>
  92. /// <param name='id'>The ID of the agent being teleported</param>
  93. /// <returns>true if the agent was not already in transit, false if it was</returns>
  94. internal bool SetInTransit(UUID id)
  95. {
  96. // m_log.DebugFormat("{0} SetInTransit. agent={1}, newState=Preparing", LogHeader, id);
  97. lock (m_agentsInTransit)
  98. {
  99. if (!m_agentsInTransit.ContainsKey(id))
  100. {
  101. m_agentsInTransit[id] = AgentTransferState.Preparing;
  102. return true;
  103. }
  104. }
  105. return false;
  106. }
  107. /// <summary>
  108. /// Updates the state of an agent that is already in transit.
  109. /// </summary>
  110. /// <param name='id'></param>
  111. /// <param name='newState'></param>
  112. /// <returns></returns>
  113. /// <exception cref='Exception'>Illegal transitions will throw an Exception</exception>
  114. internal bool UpdateInTransit(UUID id, AgentTransferState newState)
  115. {
  116. // m_log.DebugFormat("{0} UpdateInTransit. agent={1}, newState={2}", LogHeader, id, newState);
  117. bool transitionOkay = false;
  118. // We don't want to throw an exception on cancel since this can come it at any time.
  119. bool failIfNotOkay = true;
  120. // Should be a failure message if failure is not okay.
  121. string failureMessage = null;
  122. AgentTransferState? oldState = null;
  123. lock (m_agentsInTransit)
  124. {
  125. // Illegal to try and update an agent that's not actually in transit.
  126. if (!m_agentsInTransit.ContainsKey(id))
  127. {
  128. if (newState != AgentTransferState.Cancelling && newState != AgentTransferState.Aborting)
  129. failureMessage = string.Format(
  130. "Agent with ID {0} is not registered as in transit in {1}",
  131. id, m_mod.Scene.RegionInfo.RegionName);
  132. else
  133. failIfNotOkay = false;
  134. }
  135. else
  136. {
  137. oldState = m_agentsInTransit[id];
  138. if (newState == AgentTransferState.Aborting)
  139. {
  140. transitionOkay = true;
  141. }
  142. else if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp)
  143. {
  144. transitionOkay = true;
  145. }
  146. else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing)
  147. {
  148. transitionOkay = true;
  149. }
  150. else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring)
  151. {
  152. transitionOkay = true;
  153. }
  154. else
  155. {
  156. if (newState == AgentTransferState.Cancelling
  157. && (oldState == AgentTransferState.Preparing || oldState == AgentTransferState.Transferring))
  158. {
  159. transitionOkay = true;
  160. }
  161. else
  162. {
  163. failIfNotOkay = false;
  164. }
  165. }
  166. if (!transitionOkay)
  167. failureMessage
  168. = string.Format(
  169. "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}",
  170. id, oldState, newState, m_mod.Scene.RegionInfo.RegionName);
  171. }
  172. if (transitionOkay)
  173. {
  174. m_agentsInTransit[id] = newState;
  175. // m_log.DebugFormat(
  176. // "[ENTITY TRANSFER STATE MACHINE]: Changed agent with id {0} from state {1} to {2} in {3}",
  177. // id, oldState, newState, m_mod.Scene.Name);
  178. }
  179. else if (failIfNotOkay)
  180. {
  181. m_log.DebugFormat("{0} UpdateInTransit. Throwing transition failure = {1}", LogHeader, failureMessage);
  182. throw new Exception(failureMessage);
  183. }
  184. // else
  185. // {
  186. // if (oldState != null)
  187. // m_log.DebugFormat(
  188. // "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} from state {1} to {2} in {3}",
  189. // id, oldState, newState, m_mod.Scene.Name);
  190. // else
  191. // m_log.DebugFormat(
  192. // "[ENTITY TRANSFER STATE MACHINE]: Ignored change of agent with id {0} to state {1} in {2} since agent not in transit",
  193. // id, newState, m_mod.Scene.Name);
  194. // }
  195. }
  196. return transitionOkay;
  197. }
  198. /// <summary>
  199. /// Gets the current agent transfer state.
  200. /// </summary>
  201. /// <returns>Null if the agent is not in transit</returns>
  202. /// <param name='id'>
  203. /// Identifier.
  204. /// </param>
  205. internal AgentTransferState? GetAgentTransferState(UUID id)
  206. {
  207. lock (m_agentsInTransit)
  208. {
  209. if (!m_agentsInTransit.ContainsKey(id))
  210. return null;
  211. else
  212. return m_agentsInTransit[id];
  213. }
  214. }
  215. /// <summary>
  216. /// Removes an agent from the transit state machine.
  217. /// </summary>
  218. /// <param name='id'></param>
  219. /// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns>
  220. internal bool ResetFromTransit(UUID id)
  221. {
  222. lock (m_agentsInTransit)
  223. {
  224. if (m_agentsInTransit.ContainsKey(id))
  225. {
  226. AgentTransferState state = m_agentsInTransit[id];
  227. // if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination)
  228. // {
  229. // FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed
  230. // to be handled properly - ResetFromTransit() could be invoked at any step along the process
  231. // m_log.WarnFormat(
  232. // "[ENTITY TRANSFER STATE MACHINE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first in {3}",
  233. // id, state, AgentTransferState.CleaningUp, m_mod.Scene.RegionInfo.RegionName);
  234. // throw new Exception(
  235. // "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first",
  236. // state, AgentTransferState.CleaningUp);
  237. // }
  238. m_agentsInTransit.Remove(id);
  239. // m_log.DebugFormat(
  240. // "[ENTITY TRANSFER STATE MACHINE]: Agent {0} cleared from transit in {1}",
  241. // id, m_mod.Scene.RegionInfo.RegionName);
  242. return true;
  243. }
  244. }
  245. // m_log.WarnFormat(
  246. // "[ENTITY TRANSFER STATE MACHINE]: Agent {0} requested to clear from transit in {1} but was already cleared",
  247. // id, m_mod.Scene.RegionInfo.RegionName);
  248. return false;
  249. }
  250. internal bool WaitForAgentArrivedAtDestination(UUID id)
  251. {
  252. if (!m_mod.WaitForAgentArrivedAtDestination)
  253. return true;
  254. lock (m_agentsInTransit)
  255. {
  256. AgentTransferState? currentState = GetAgentTransferState(id);
  257. if (currentState == null)
  258. throw new Exception(
  259. string.Format(
  260. "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit",
  261. id, m_mod.Scene.RegionInfo.RegionName));
  262. if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination)
  263. throw new Exception(
  264. string.Format(
  265. "Asked to wait for destination callback for agent with ID {0} in {1} but agent is in state {2}",
  266. id, m_mod.Scene.RegionInfo.RegionName, currentState));
  267. }
  268. int count = 400;
  269. // There should be no race condition here since no other code should be removing the agent transfer or
  270. // changing the state to another other than Transferring => ReceivedAtDestination.
  271. while (count-- > 0)
  272. {
  273. lock (m_agentsInTransit)
  274. {
  275. if (m_agentsInTransit[id] == AgentTransferState.ReceivedAtDestination)
  276. break;
  277. }
  278. // m_log.Debug(" >>> Waiting... " + count);
  279. Thread.Sleep(100);
  280. }
  281. return count > 0;
  282. }
  283. internal void SetAgentArrivedAtDestination(UUID id)
  284. {
  285. lock (m_agentsInTransit)
  286. {
  287. if (!m_agentsInTransit.ContainsKey(id))
  288. {
  289. m_log.WarnFormat(
  290. "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but no teleport request is active",
  291. m_mod.Scene.RegionInfo.RegionName, id);
  292. return;
  293. }
  294. AgentTransferState currentState = m_agentsInTransit[id];
  295. if (currentState == AgentTransferState.ReceivedAtDestination)
  296. {
  297. // An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification.
  298. m_log.WarnFormat(
  299. "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but notification has already previously been received",
  300. m_mod.Scene.RegionInfo.RegionName, id);
  301. }
  302. else if (currentState != AgentTransferState.Transferring)
  303. {
  304. m_log.ErrorFormat(
  305. "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but agent is in state {2}",
  306. m_mod.Scene.RegionInfo.RegionName, id, currentState);
  307. return;
  308. }
  309. m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination;
  310. }
  311. }
  312. }
  313. }