diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs | 199 |
1 files changed, 149 insertions, 50 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs index 46337b3..79e35f4 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs | |||
@@ -147,23 +147,36 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
147 | private int m_elapsed500MSOutgoingPacketHandler; | 147 | private int m_elapsed500MSOutgoingPacketHandler; |
148 | 148 | ||
149 | /// <summary>Flag to signal when clients should check for resends</summary> | 149 | /// <summary>Flag to signal when clients should check for resends</summary> |
150 | private bool m_resendUnacked; | 150 | protected bool m_resendUnacked; |
151 | |||
151 | /// <summary>Flag to signal when clients should send ACKs</summary> | 152 | /// <summary>Flag to signal when clients should send ACKs</summary> |
152 | private bool m_sendAcks; | 153 | protected bool m_sendAcks; |
154 | |||
153 | /// <summary>Flag to signal when clients should send pings</summary> | 155 | /// <summary>Flag to signal when clients should send pings</summary> |
154 | private bool m_sendPing; | 156 | protected bool m_sendPing; |
155 | 157 | ||
156 | private ExpiringCache<IPEndPoint, Queue<UDPPacketBuffer>> m_pendingCache = new ExpiringCache<IPEndPoint, Queue<UDPPacketBuffer>>(); | 158 | private ExpiringCache<IPEndPoint, Queue<UDPPacketBuffer>> m_pendingCache = new ExpiringCache<IPEndPoint, Queue<UDPPacketBuffer>>(); |
157 | 159 | ||
158 | private int m_defaultRTO = 0; | 160 | private int m_defaultRTO = 0; |
159 | private int m_maxRTO = 0; | 161 | private int m_maxRTO = 0; |
160 | 162 | private int m_ackTimeout = 0; | |
163 | private int m_pausedAckTimeout = 0; | ||
161 | private bool m_disableFacelights = false; | 164 | private bool m_disableFacelights = false; |
162 | 165 | ||
163 | public Socket Server { get { return null; } } | 166 | public Socket Server { get { return null; } } |
164 | 167 | ||
165 | private int m_malformedCount = 0; // Guard against a spamming attack | 168 | private int m_malformedCount = 0; // Guard against a spamming attack |
166 | 169 | ||
170 | /// <summary> | ||
171 | /// Record current outgoing client for monitoring purposes. | ||
172 | /// </summary> | ||
173 | private IClientAPI m_currentOutgoingClient; | ||
174 | |||
175 | /// <summary> | ||
176 | /// Recording current incoming client for monitoring purposes. | ||
177 | /// </summary> | ||
178 | private IClientAPI m_currentIncomingClient; | ||
179 | |||
167 | public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) | 180 | public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) |
168 | : base(listenIP, (int)port) | 181 | : base(listenIP, (int)port) |
169 | { | 182 | { |
@@ -200,11 +213,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
200 | m_defaultRTO = config.GetInt("DefaultRTO", 0); | 213 | m_defaultRTO = config.GetInt("DefaultRTO", 0); |
201 | m_maxRTO = config.GetInt("MaxRTO", 0); | 214 | m_maxRTO = config.GetInt("MaxRTO", 0); |
202 | m_disableFacelights = config.GetBoolean("DisableFacelights", false); | 215 | m_disableFacelights = config.GetBoolean("DisableFacelights", false); |
216 | m_ackTimeout = 1000 * config.GetInt("AckTimeout", 60); | ||
217 | m_pausedAckTimeout = 1000 * config.GetInt("PausedAckTimeout", 300); | ||
203 | } | 218 | } |
204 | else | 219 | else |
205 | { | 220 | { |
206 | PrimUpdatesPerCallback = 100; | 221 | PrimUpdatesPerCallback = 100; |
207 | TextureSendLimit = 20; | 222 | TextureSendLimit = 20; |
223 | m_ackTimeout = 1000 * 60; // 1 minute | ||
224 | m_pausedAckTimeout = 1000 * 300; // 5 minutes | ||
208 | } | 225 | } |
209 | 226 | ||
210 | #region BinaryStats | 227 | #region BinaryStats |
@@ -241,19 +258,56 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
241 | if (m_scene == null) | 258 | if (m_scene == null) |
242 | throw new InvalidOperationException("[LLUDPSERVER]: Cannot LLUDPServer.Start() without an IScene reference"); | 259 | throw new InvalidOperationException("[LLUDPSERVER]: Cannot LLUDPServer.Start() without an IScene reference"); |
243 | 260 | ||
244 | m_log.Info("[LLUDPSERVER]: Starting the LLUDP server in " + (m_asyncPacketHandling ? "asynchronous" : "synchronous") + " mode"); | 261 | m_log.InfoFormat( |
262 | "[LLUDPSERVER]: Starting the LLUDP server in {0} mode", | ||
263 | m_asyncPacketHandling ? "asynchronous" : "synchronous"); | ||
245 | 264 | ||
246 | base.Start(m_recvBufferSize, m_asyncPacketHandling); | 265 | base.Start(m_recvBufferSize, m_asyncPacketHandling); |
247 | 266 | ||
248 | // Start the packet processing threads | 267 | // Start the packet processing threads |
249 | Watchdog.StartThread( | 268 | Watchdog.StartThread( |
250 | IncomingPacketHandler, "Incoming Packets (" + m_scene.RegionInfo.RegionName + ")", ThreadPriority.Normal, false, true); | 269 | IncomingPacketHandler, |
270 | string.Format("Incoming Packets ({0})", m_scene.RegionInfo.RegionName), | ||
271 | ThreadPriority.Normal, | ||
272 | false, | ||
273 | true, | ||
274 | GetWatchdogIncomingAlarmData, | ||
275 | Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS); | ||
276 | |||
251 | Watchdog.StartThread( | 277 | Watchdog.StartThread( |
252 | OutgoingPacketHandler, "Outgoing Packets (" + m_scene.RegionInfo.RegionName + ")", ThreadPriority.Normal, false, true); | 278 | OutgoingPacketHandler, |
279 | string.Format("Outgoing Packets ({0})", m_scene.RegionInfo.RegionName), | ||
280 | ThreadPriority.Normal, | ||
281 | false, | ||
282 | true, | ||
283 | GetWatchdogOutgoingAlarmData, | ||
284 | Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS); | ||
253 | 285 | ||
254 | m_elapsedMSSinceLastStatReport = Environment.TickCount; | 286 | m_elapsedMSSinceLastStatReport = Environment.TickCount; |
255 | } | 287 | } |
256 | 288 | ||
289 | /// <summary> | ||
290 | /// If the outgoing UDP thread times out, then return client that was being processed to help with debugging. | ||
291 | /// </summary> | ||
292 | /// <returns></returns> | ||
293 | private string GetWatchdogIncomingAlarmData() | ||
294 | { | ||
295 | return string.Format( | ||
296 | "Client is {0}", | ||
297 | m_currentIncomingClient != null ? m_currentIncomingClient.Name : "none"); | ||
298 | } | ||
299 | |||
300 | /// <summary> | ||
301 | /// If the outgoing UDP thread times out, then return client that was being processed to help with debugging. | ||
302 | /// </summary> | ||
303 | /// <returns></returns> | ||
304 | private string GetWatchdogOutgoingAlarmData() | ||
305 | { | ||
306 | return string.Format( | ||
307 | "Client is {0}", | ||
308 | m_currentOutgoingClient != null ? m_currentOutgoingClient.Name : "none"); | ||
309 | } | ||
310 | |||
257 | public new void Stop() | 311 | public new void Stop() |
258 | { | 312 | { |
259 | m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + m_scene.RegionInfo.RegionName); | 313 | m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + m_scene.RegionInfo.RegionName); |
@@ -487,19 +541,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
487 | SendPacket(udpClient, completePing, ThrottleOutPacketType.Unknown, false, null); | 541 | SendPacket(udpClient, completePing, ThrottleOutPacketType.Unknown, false, null); |
488 | } | 542 | } |
489 | 543 | ||
490 | public void HandleUnacked(LLUDPClient udpClient) | 544 | public void HandleUnacked(LLClientView client) |
491 | { | 545 | { |
546 | LLUDPClient udpClient = client.UDPClient; | ||
547 | |||
492 | if (!udpClient.IsConnected) | 548 | if (!udpClient.IsConnected) |
493 | return; | 549 | return; |
494 | 550 | ||
495 | // Disconnect an agent if no packets are received for some time | 551 | // Disconnect an agent if no packets are received for some time |
496 | //FIXME: Make 60 an .ini setting | 552 | int timeoutTicks = m_ackTimeout; |
497 | if ((Environment.TickCount & Int32.MaxValue) - udpClient.TickLastPacketReceived > 1000 * 60) | 553 | |
554 | // Allow more slack if the client is "paused" eg file upload dialogue is open | ||
555 | // Some sort of limit is needed in case the client crashes, loses its network connection | ||
556 | // or some other disaster prevents it from sendung the AgentResume | ||
557 | if (udpClient.IsPaused) | ||
558 | timeoutTicks = m_pausedAckTimeout; | ||
559 | |||
560 | if (client.IsActive && | ||
561 | (Environment.TickCount & Int32.MaxValue) - udpClient.TickLastPacketReceived > timeoutTicks) | ||
498 | { | 562 | { |
499 | m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + udpClient.AgentID); | 563 | // We must set IsActive synchronously so that we can stop the packet loop reinvoking this method, even |
500 | StatsManager.SimExtraStats.AddAbnormalClientThreadTermination(); | 564 | // though it's set later on by LLClientView.Close() |
565 | client.IsActive = false; | ||
566 | |||
567 | // Fire this out on a different thread so that we don't hold up outgoing packet processing for | ||
568 | // everybody else if this is being called due to an ack timeout. | ||
569 | // This is the same as processing as the async process of a logout request. | ||
570 | Util.FireAndForget(o => DeactivateClientDueToTimeout(client)); | ||
501 | 571 | ||
502 | RemoveClient(udpClient); | ||
503 | return; | 572 | return; |
504 | } | 573 | } |
505 | 574 | ||
@@ -850,7 +919,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
850 | #endregion Ping Check Handling | 919 | #endregion Ping Check Handling |
851 | 920 | ||
852 | // Inbox insertion | 921 | // Inbox insertion |
853 | packetInbox.Enqueue(new IncomingPacket(udpClient, packet)); | 922 | packetInbox.Enqueue(new IncomingPacket((LLClientView)client, packet)); |
854 | } | 923 | } |
855 | 924 | ||
856 | #region BinaryStats | 925 | #region BinaryStats |
@@ -946,7 +1015,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
946 | UDPPacketBuffer buffer = (UDPPacketBuffer)array[0]; | 1015 | UDPPacketBuffer buffer = (UDPPacketBuffer)array[0]; |
947 | UseCircuitCodePacket uccp = (UseCircuitCodePacket)array[1]; | 1016 | UseCircuitCodePacket uccp = (UseCircuitCodePacket)array[1]; |
948 | 1017 | ||
949 | m_log.DebugFormat("[LLUDPSERVER]: Handling UseCircuitCode request from {0}", buffer.RemoteEndPoint); | 1018 | m_log.DebugFormat( |
1019 | "[LLUDPSERVER]: Handling UseCircuitCode request for circuit {0} to {1} from IP {2}", | ||
1020 | uccp.CircuitCode.Code, m_scene.RegionInfo.RegionName, buffer.RemoteEndPoint); | ||
950 | 1021 | ||
951 | remoteEndPoint = (IPEndPoint)buffer.RemoteEndPoint; | 1022 | remoteEndPoint = (IPEndPoint)buffer.RemoteEndPoint; |
952 | 1023 | ||
@@ -1001,8 +1072,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1001 | { | 1072 | { |
1002 | // Don't create clients for unauthorized requesters. | 1073 | // Don't create clients for unauthorized requesters. |
1003 | m_log.WarnFormat( | 1074 | m_log.WarnFormat( |
1004 | "[LLUDPSERVER]: Connection request for client {0} connecting with unnotified circuit code {1} from {2}", | 1075 | "[LLUDPSERVER]: Ignoring connection request for {0} to {1} with unknown circuit code {2} from IP {3}", |
1005 | uccp.CircuitCode.ID, uccp.CircuitCode.Code, remoteEndPoint); | 1076 | uccp.CircuitCode.ID, m_scene.RegionInfo.RegionName, uccp.CircuitCode.Code, remoteEndPoint); |
1006 | lock (m_pendingCache) | 1077 | lock (m_pendingCache) |
1007 | m_pendingCache.Remove(remoteEndPoint); | 1078 | m_pendingCache.Remove(remoteEndPoint); |
1008 | } | 1079 | } |
@@ -1090,7 +1161,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1090 | { | 1161 | { |
1091 | LLUDPClient udpClient = new LLUDPClient(this, ThrottleRates, m_throttle, circuitCode, agentID, remoteEndPoint, m_defaultRTO, m_maxRTO); | 1162 | LLUDPClient udpClient = new LLUDPClient(this, ThrottleRates, m_throttle, circuitCode, agentID, remoteEndPoint, m_defaultRTO, m_maxRTO); |
1092 | 1163 | ||
1093 | client = new LLClientView(remoteEndPoint, m_scene, this, udpClient, sessionInfo, agentID, sessionID, circuitCode); | 1164 | client = new LLClientView(m_scene, this, udpClient, sessionInfo, agentID, sessionID, circuitCode); |
1094 | client.OnLogout += LogoutHandler; | 1165 | client.OnLogout += LogoutHandler; |
1095 | 1166 | ||
1096 | ((LLClientView)client).DisableFacelights = m_disableFacelights; | 1167 | ((LLClientView)client).DisableFacelights = m_disableFacelights; |
@@ -1102,15 +1173,30 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1102 | return client; | 1173 | return client; |
1103 | } | 1174 | } |
1104 | 1175 | ||
1105 | private void RemoveClient(LLUDPClient udpClient) | 1176 | /// <summary> |
1177 | /// Deactivates the client if we don't receive any packets within a certain amount of time (default 60 seconds). | ||
1178 | /// </summary> | ||
1179 | /// <remarks> | ||
1180 | /// If a connection is active then we will always receive packets even if nothing else is happening, due to | ||
1181 | /// regular client pings. | ||
1182 | /// </remarks> | ||
1183 | /// <param name='client'></param> | ||
1184 | private void DeactivateClientDueToTimeout(IClientAPI client) | ||
1106 | { | 1185 | { |
1107 | // Remove this client from the scene | 1186 | // We must set IsActive synchronously so that we can stop the packet loop reinvoking this method, even |
1108 | IClientAPI client; | 1187 | // though it's set later on by LLClientView.Close() |
1109 | if (m_scene.TryGetClient(udpClient.AgentID, out client)) | 1188 | client.IsActive = false; |
1110 | { | 1189 | |
1111 | client.IsLoggingOut = true; | 1190 | m_log.WarnFormat( |
1112 | client.Close(false); | 1191 | "[LLUDPSERVER]: Ack timeout, disconnecting {0} agent for {1} in {2}", |
1113 | } | 1192 | client.SceneAgent.IsChildAgent ? "child" : "root", client.Name, m_scene.RegionInfo.RegionName); |
1193 | |||
1194 | StatsManager.SimExtraStats.AddAbnormalClientThreadTermination(); | ||
1195 | |||
1196 | if (!client.SceneAgent.IsChildAgent) | ||
1197 | client.Kick("Simulator logged you out due to connection timeout"); | ||
1198 | |||
1199 | Util.FireAndForget(o => client.Close()); | ||
1114 | } | 1200 | } |
1115 | 1201 | ||
1116 | private void IncomingPacketHandler() | 1202 | private void IncomingPacketHandler() |
@@ -1219,6 +1305,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1219 | // client. m_packetSent will be set to true if a packet is sent | 1305 | // client. m_packetSent will be set to true if a packet is sent |
1220 | m_scene.ForEachClient(clientPacketHandler); | 1306 | m_scene.ForEachClient(clientPacketHandler); |
1221 | 1307 | ||
1308 | m_currentOutgoingClient = null; | ||
1309 | |||
1222 | // If nothing was sent, sleep for the minimum amount of time before a | 1310 | // If nothing was sent, sleep for the minimum amount of time before a |
1223 | // token bucket could get more tokens | 1311 | // token bucket could get more tokens |
1224 | if (!m_packetSent) | 1312 | if (!m_packetSent) |
@@ -1235,18 +1323,21 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1235 | Watchdog.RemoveThread(); | 1323 | Watchdog.RemoveThread(); |
1236 | } | 1324 | } |
1237 | 1325 | ||
1238 | private void ClientOutgoingPacketHandler(IClientAPI client) | 1326 | protected void ClientOutgoingPacketHandler(IClientAPI client) |
1239 | { | 1327 | { |
1328 | m_currentOutgoingClient = client; | ||
1329 | |||
1240 | try | 1330 | try |
1241 | { | 1331 | { |
1242 | if (client is LLClientView) | 1332 | if (client is LLClientView) |
1243 | { | 1333 | { |
1244 | LLUDPClient udpClient = ((LLClientView)client).UDPClient; | 1334 | LLClientView llClient = (LLClientView)client; |
1335 | LLUDPClient udpClient = llClient.UDPClient; | ||
1245 | 1336 | ||
1246 | if (udpClient.IsConnected) | 1337 | if (udpClient.IsConnected) |
1247 | { | 1338 | { |
1248 | if (m_resendUnacked) | 1339 | if (m_resendUnacked) |
1249 | HandleUnacked(udpClient); | 1340 | HandleUnacked(llClient); |
1250 | 1341 | ||
1251 | if (m_sendAcks) | 1342 | if (m_sendAcks) |
1252 | SendAcks(udpClient); | 1343 | SendAcks(udpClient); |
@@ -1262,8 +1353,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1262 | } | 1353 | } |
1263 | catch (Exception ex) | 1354 | catch (Exception ex) |
1264 | { | 1355 | { |
1265 | m_log.Error("[LLUDPSERVER]: OutgoingPacketHandler iteration for " + client.Name + | 1356 | m_log.Error( |
1266 | " threw an exception: " + ex.Message, ex); | 1357 | string.Format("[LLUDPSERVER]: OutgoingPacketHandler iteration for {0} threw ", client.Name), ex); |
1267 | } | 1358 | } |
1268 | } | 1359 | } |
1269 | 1360 | ||
@@ -1289,11 +1380,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1289 | { | 1380 | { |
1290 | nticks++; | 1381 | nticks++; |
1291 | watch1.Start(); | 1382 | watch1.Start(); |
1383 | m_currentOutgoingClient = client; | ||
1384 | |||
1292 | try | 1385 | try |
1293 | { | 1386 | { |
1294 | if (client is LLClientView) | 1387 | if (client is LLClientView) |
1295 | { | 1388 | { |
1296 | LLUDPClient udpClient = ((LLClientView)client).UDPClient; | 1389 | LLClientView llClient = (LLClientView)client; |
1390 | LLUDPClient udpClient = llClient.UDPClient; | ||
1297 | 1391 | ||
1298 | if (udpClient.IsConnected) | 1392 | if (udpClient.IsConnected) |
1299 | { | 1393 | { |
@@ -1302,7 +1396,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1302 | nticksUnack++; | 1396 | nticksUnack++; |
1303 | watch2.Start(); | 1397 | watch2.Start(); |
1304 | 1398 | ||
1305 | HandleUnacked(udpClient); | 1399 | HandleUnacked(llClient); |
1306 | 1400 | ||
1307 | watch2.Stop(); | 1401 | watch2.Stop(); |
1308 | avgResendUnackedTicks = (nticksUnack - 1)/(float)nticksUnack * avgResendUnackedTicks + (watch2.ElapsedTicks / (float)nticksUnack); | 1402 | avgResendUnackedTicks = (nticksUnack - 1)/(float)nticksUnack * avgResendUnackedTicks + (watch2.ElapsedTicks / (float)nticksUnack); |
@@ -1373,23 +1467,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1373 | 1467 | ||
1374 | #endregion | 1468 | #endregion |
1375 | 1469 | ||
1376 | private void ProcessInPacket(object state) | 1470 | private void ProcessInPacket(IncomingPacket incomingPacket) |
1377 | { | 1471 | { |
1378 | IncomingPacket incomingPacket = (IncomingPacket)state; | ||
1379 | Packet packet = incomingPacket.Packet; | 1472 | Packet packet = incomingPacket.Packet; |
1380 | LLUDPClient udpClient = incomingPacket.Client; | 1473 | LLClientView client = incomingPacket.Client; |
1381 | IClientAPI client; | ||
1382 | 1474 | ||
1383 | // Sanity check | 1475 | if (client.IsActive) |
1384 | if (packet == null || udpClient == null) | ||
1385 | { | 1476 | { |
1386 | m_log.WarnFormat("[LLUDPSERVER]: Processing a packet with incomplete state. Packet=\"{0}\", UDPClient=\"{1}\"", | 1477 | m_currentIncomingClient = client; |
1387 | packet, udpClient); | ||
1388 | } | ||
1389 | 1478 | ||
1390 | // Make sure this client is still alive | ||
1391 | if (m_scene.TryGetClient(udpClient.AgentID, out client)) | ||
1392 | { | ||
1393 | try | 1479 | try |
1394 | { | 1480 | { |
1395 | // Process this packet | 1481 | // Process this packet |
@@ -1404,21 +1490,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
1404 | catch (Exception e) | 1490 | catch (Exception e) |
1405 | { | 1491 | { |
1406 | // Don't let a failure in an individual client thread crash the whole sim. | 1492 | // Don't let a failure in an individual client thread crash the whole sim. |
1407 | m_log.ErrorFormat("[LLUDPSERVER]: Client packet handler for {0} for packet {1} threw an exception", udpClient.AgentID, packet.Type); | 1493 | m_log.Error( |
1408 | m_log.Error(e.Message, e); | 1494 | string.Format( |
1495 | "[LLUDPSERVER]: Client packet handler for {0} for packet {1} threw ", | ||
1496 | client.Name, packet.Type), | ||
1497 | e); | ||
1498 | } | ||
1499 | finally | ||
1500 | { | ||
1501 | m_currentIncomingClient = null; | ||
1409 | } | 1502 | } |
1410 | } | 1503 | } |
1411 | else | 1504 | else |
1412 | { | 1505 | { |
1413 | m_log.DebugFormat("[LLUDPSERVER]: Dropping incoming {0} packet for dead client {1}", packet.Type, udpClient.AgentID); | 1506 | m_log.DebugFormat( |
1507 | "[LLUDPSERVER]: Dropped incoming {0} for dead client {1} in {2}", | ||
1508 | packet.Type, client.Name, m_scene.RegionInfo.RegionName); | ||
1414 | } | 1509 | } |
1415 | } | 1510 | } |
1416 | 1511 | ||
1417 | protected void LogoutHandler(IClientAPI client) | 1512 | protected void LogoutHandler(IClientAPI client) |
1418 | { | 1513 | { |
1419 | client.SendLogoutPacket(); | 1514 | client.SendLogoutPacket(); |
1420 | if (client.IsActive) | 1515 | |
1421 | RemoveClient(((LLClientView)client).UDPClient); | 1516 | if (!client.IsLoggingOut) |
1517 | { | ||
1518 | client.IsLoggingOut = true; | ||
1519 | client.Close(); | ||
1520 | } | ||
1422 | } | 1521 | } |
1423 | } | 1522 | } |
1424 | } | 1523 | } |