diff options
author | Justin Clark-Casey (justincc) | 2012-05-28 23:06:00 +0100 |
---|---|---|
committer | Justin Clark-Casey (justincc) | 2012-05-28 23:06:00 +0100 |
commit | 79f3ce2e9f3563165ee640f917f944ec4b370ac7 (patch) | |
tree | bab16f99077a5145ca87d16e753d973b14527361 /OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs | |
parent | If handling the failure of teleport, move agent state to CleaningUp when we s... (diff) | |
download | opensim-SC_OLD-79f3ce2e9f3563165ee640f917f944ec4b370ac7.zip opensim-SC_OLD-79f3ce2e9f3563165ee640f917f944ec4b370ac7.tar.gz opensim-SC_OLD-79f3ce2e9f3563165ee640f917f944ec4b370ac7.tar.bz2 opensim-SC_OLD-79f3ce2e9f3563165ee640f917f944ec4b370ac7.tar.xz |
refactor: factor out entity transfer state machine into a separate class to make code more analyzable
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs new file mode 100644 index 0000000..d0cab49 --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferStateMachine.cs | |||
@@ -0,0 +1,269 @@ | |||
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 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | using System.Threading; | ||
33 | using OpenMetaverse; | ||
34 | using log4net; | ||
35 | using Nini.Config; | ||
36 | using OpenSim.Framework; | ||
37 | using OpenSim.Framework.Capabilities; | ||
38 | using OpenSim.Framework.Client; | ||
39 | using OpenSim.Region.Framework.Interfaces; | ||
40 | using OpenSim.Region.Framework.Scenes; | ||
41 | using OpenSim.Region.Physics.Manager; | ||
42 | using OpenSim.Services.Interfaces; | ||
43 | using GridRegion = OpenSim.Services.Interfaces.GridRegion; | ||
44 | |||
45 | namespace OpenSim.Region.CoreModules.Framework.EntityTransfer | ||
46 | { | ||
47 | /// <summary> | ||
48 | /// The possible states that an agent can be in when its being transferred between regions. | ||
49 | /// </summary> | ||
50 | /// <remarks> | ||
51 | /// This is a state machine. | ||
52 | /// | ||
53 | /// [Entry] => Preparing | ||
54 | /// Preparing => { Transferring || CleaningUp || [Exit] } | ||
55 | /// Transferring => { ReceivedAtDestination || CleaningUp } | ||
56 | /// ReceivedAtDestination => CleaningUp | ||
57 | /// CleaningUp => [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 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// Records the state of entities when they are in transfer within or between regions (cross or teleport). | ||
72 | /// </summary> | ||
73 | public class EntityTransferStateMachine | ||
74 | { | ||
75 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
76 | |||
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 | |||
83 | private EntityTransferModule m_mod; | ||
84 | |||
85 | private Dictionary<UUID, AgentTransferState> m_agentsInTransit = new Dictionary<UUID, AgentTransferState>(); | ||
86 | |||
87 | public EntityTransferStateMachine(EntityTransferModule module) | ||
88 | { | ||
89 | m_mod = module; | ||
90 | } | ||
91 | |||
92 | /// <summary> | ||
93 | /// Set that an agent is in transit. | ||
94 | /// </summary> | ||
95 | /// <param name='id'>The ID of the agent being teleported</param> | ||
96 | /// <returns>true if the agent was not already in transit, false if it was</returns> | ||
97 | internal bool SetInTransit(UUID id) | ||
98 | { | ||
99 | lock (m_agentsInTransit) | ||
100 | { | ||
101 | if (!m_agentsInTransit.ContainsKey(id)) | ||
102 | { | ||
103 | m_agentsInTransit[id] = AgentTransferState.Preparing; | ||
104 | return true; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | return false; | ||
109 | } | ||
110 | |||
111 | /// <summary> | ||
112 | /// Updates the state of an agent that is already in transit. | ||
113 | /// </summary> | ||
114 | /// <param name='id'></param> | ||
115 | /// <param name='newState'></param> | ||
116 | /// <returns></returns> | ||
117 | /// <exception cref='Exception'>Illegal transitions will throw an Exception</exception> | ||
118 | internal void UpdateInTransit(UUID id, AgentTransferState newState) | ||
119 | { | ||
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 | throw new Exception( | ||
125 | string.Format( | ||
126 | "Agent with ID {0} is not registered as in transit in {1}", | ||
127 | id, m_mod.Scene.RegionInfo.RegionName)); | ||
128 | |||
129 | AgentTransferState oldState = m_agentsInTransit[id]; | ||
130 | |||
131 | bool transitionOkay = false; | ||
132 | |||
133 | if (newState == AgentTransferState.CleaningUp && oldState != AgentTransferState.CleaningUp) | ||
134 | transitionOkay = true; | ||
135 | else if (newState == AgentTransferState.Transferring && oldState == AgentTransferState.Preparing) | ||
136 | transitionOkay = true; | ||
137 | else if (newState == AgentTransferState.ReceivedAtDestination && oldState == AgentTransferState.Transferring) | ||
138 | transitionOkay = true; | ||
139 | |||
140 | if (transitionOkay) | ||
141 | m_agentsInTransit[id] = newState; | ||
142 | else | ||
143 | throw new Exception( | ||
144 | string.Format( | ||
145 | "Agent with ID {0} is not allowed to move from old transit state {1} to new state {2} in {3}", | ||
146 | id, oldState, newState, m_mod.Scene.RegionInfo.RegionName)); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | internal bool IsInTransit(UUID id) | ||
151 | { | ||
152 | lock (m_agentsInTransit) | ||
153 | return m_agentsInTransit.ContainsKey(id); | ||
154 | } | ||
155 | |||
156 | /// <summary> | ||
157 | /// Removes an agent from the transit state machine. | ||
158 | /// </summary> | ||
159 | /// <param name='id'></param> | ||
160 | /// <returns>true if the agent was flagged as being teleported when this method was called, false otherwise</returns> | ||
161 | internal bool ResetFromTransit(UUID id) | ||
162 | { | ||
163 | lock (m_agentsInTransit) | ||
164 | { | ||
165 | if (m_agentsInTransit.ContainsKey(id)) | ||
166 | { | ||
167 | AgentTransferState state = m_agentsInTransit[id]; | ||
168 | |||
169 | if (state == AgentTransferState.Transferring || state == AgentTransferState.ReceivedAtDestination) | ||
170 | { | ||
171 | // FIXME: For now, we allow exit from any state since a thrown exception in teleport is now guranteed | ||
172 | // to be handled properly - ResetFromTransit() could be invoked at any step along the process | ||
173 | m_log.WarnFormat( | ||
174 | "[ENTITY TRANSFER STATE MACHINE]: Agent with ID {0} should not exit directly from state {1}, should go to {2} state first in {3}", | ||
175 | id, state, AgentTransferState.CleaningUp, m_mod.Scene.RegionInfo.RegionName); | ||
176 | |||
177 | // throw new Exception( | ||
178 | // "Agent with ID {0} cannot exit directly from state {1}, it must go to {2} state first", | ||
179 | // state, AgentTransferState.CleaningUp); | ||
180 | } | ||
181 | |||
182 | m_agentsInTransit.Remove(id); | ||
183 | |||
184 | m_log.DebugFormat( | ||
185 | "[ENTITY TRANSFER STATE MACHINE]: Agent {0} cleared from transit in {1}", | ||
186 | id, m_mod.Scene.RegionInfo.RegionName); | ||
187 | |||
188 | return true; | ||
189 | } | ||
190 | } | ||
191 | |||
192 | m_log.WarnFormat( | ||
193 | "[ENTITY TRANSFER STATE MACHINE]: Agent {0} requested to clear from transit in {1} but was already cleared", | ||
194 | id, m_mod.Scene.RegionInfo.RegionName); | ||
195 | |||
196 | return false; | ||
197 | } | ||
198 | |||
199 | internal bool WaitForAgentArrivedAtDestination(UUID id) | ||
200 | { | ||
201 | if (!m_mod.WaitForAgentArrivedAtDestination) | ||
202 | return true; | ||
203 | |||
204 | lock (m_agentsInTransit) | ||
205 | { | ||
206 | if (!IsInTransit(id)) | ||
207 | throw new Exception( | ||
208 | string.Format( | ||
209 | "Asked to wait for destination callback for agent with ID {0} in {1} but agent is not in transit", | ||
210 | id, m_mod.Scene.RegionInfo.RegionName)); | ||
211 | |||
212 | AgentTransferState currentState = m_agentsInTransit[id]; | ||
213 | |||
214 | if (currentState != AgentTransferState.Transferring && currentState != AgentTransferState.ReceivedAtDestination) | ||
215 | throw new Exception( | ||
216 | string.Format( | ||
217 | "Asked to wait for destination callback for agent with ID {0} in {1} but agent is in state {2}", | ||
218 | id, m_mod.Scene.RegionInfo.RegionName, currentState)); | ||
219 | } | ||
220 | |||
221 | int count = 200; | ||
222 | |||
223 | // There should be no race condition here since no other code should be removing the agent transfer or | ||
224 | // changing the state to another other than Transferring => ReceivedAtDestination. | ||
225 | while (m_agentsInTransit[id] != AgentTransferState.ReceivedAtDestination && count-- > 0) | ||
226 | { | ||
227 | // m_log.Debug(" >>> Waiting... " + count); | ||
228 | Thread.Sleep(100); | ||
229 | } | ||
230 | |||
231 | return count > 0; | ||
232 | } | ||
233 | |||
234 | internal void SetAgentArrivedAtDestination(UUID id) | ||
235 | { | ||
236 | lock (m_agentsInTransit) | ||
237 | { | ||
238 | if (!m_agentsInTransit.ContainsKey(id)) | ||
239 | { | ||
240 | m_log.WarnFormat( | ||
241 | "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but no teleport request is active", | ||
242 | m_mod.Scene.RegionInfo.RegionName, id); | ||
243 | |||
244 | return; | ||
245 | } | ||
246 | |||
247 | AgentTransferState currentState = m_agentsInTransit[id]; | ||
248 | |||
249 | if (currentState == AgentTransferState.ReceivedAtDestination) | ||
250 | { | ||
251 | // An anomoly but don't make this an outright failure - destination region could be overzealous in sending notification. | ||
252 | m_log.WarnFormat( | ||
253 | "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but notification has already previously been received", | ||
254 | m_mod.Scene.RegionInfo.RegionName, id); | ||
255 | } | ||
256 | else if (currentState != AgentTransferState.Transferring) | ||
257 | { | ||
258 | m_log.ErrorFormat( | ||
259 | "[ENTITY TRANSFER STATE MACHINE]: Region {0} received notification of arrival in destination of agent {1} but agent is in state {2}", | ||
260 | m_mod.Scene.RegionInfo.RegionName, id, currentState); | ||
261 | |||
262 | return; | ||
263 | } | ||
264 | |||
265 | m_agentsInTransit[id] = AgentTransferState.ReceivedAtDestination; | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | } \ No newline at end of file | ||