From bf86addf3d55aba81985c0f68aea9ee7063da403 Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Thu, 16 Jan 2014 20:23:31 +0000 Subject: Prevent duplicate invocations or race dontision in SP.CompleteMovement() This can happen under poor network conditions if a viewer repeats the message send If this happens, physics actors can get orphaned, which unecessarily raises physics frame times --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 29 ++++++- .../Scenes/Tests/ScenePresenceAgentTests.cs | 92 +++++++++------------- 2 files changed, 65 insertions(+), 56 deletions(-) (limited to 'OpenSim/Region/Framework/Scenes') diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 7243db1..d7511d3 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -108,6 +108,16 @@ namespace OpenSim.Region.Framework.Scenes } } + /// + /// This exists to prevent race conditions between two CompleteMovement threads if the simulator is slow and + /// the viewer fires these in quick succession. + /// + /// + /// TODO: The child -> agent transition should be folded into LifecycleState and the CompleteMovement + /// regulation done there. + /// + private object m_completeMovementLock = new object(); + // private static readonly byte[] DEFAULT_TEXTURE = AvatarAppearance.GetDefaultTexture().GetBytes(); private static readonly Array DIR_CONTROL_FLAGS = Enum.GetValues(typeof(Dir_ControlFlags)); private static readonly Vector3 HEAD_ADJUSTMENT = new Vector3(0f, 0f, 0.3f); @@ -904,6 +914,7 @@ namespace OpenSim.Region.Framework.Scenes /// /// Turns a child agent into a root agent. /// + /// /// Child agents are logged into neighbouring sims largely to observe changes. Root agents exist when the /// avatar is actual in the sim. They can perform all actions. /// This change is made whenever an avatar enters a region, whether by crossing over from a neighbouring sim, @@ -911,8 +922,8 @@ namespace OpenSim.Region.Framework.Scenes /// /// This method is on the critical path for transferring an avatar from one region to another. Delay here /// delays that crossing. - /// - private void MakeRootAgent(Vector3 pos, bool isFlying) + /// + private bool MakeRootAgent(Vector3 pos, bool isFlying) { // m_log.InfoFormat( // "[SCENE]: Upgrading child to root agent for {0} in {1}", @@ -920,6 +931,10 @@ namespace OpenSim.Region.Framework.Scenes //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); + lock (m_completeMovementLock) + if (!IsChildAgent) + return false; + IsChildAgent = false; // Must reset this here so that a teleport to a region next to an existing region does not keep the flag @@ -1069,6 +1084,7 @@ namespace OpenSim.Region.Framework.Scenes m_scene.EventManager.TriggerOnMakeRootAgent(this); + return true; } public int GetStateSource() @@ -1442,7 +1458,14 @@ namespace OpenSim.Region.Framework.Scenes } bool flying = ((m_AgentControlFlags & AgentManager.ControlFlags.AGENT_CONTROL_FLY) != 0); - MakeRootAgent(AbsolutePosition, flying); + if (!MakeRootAgent(AbsolutePosition, flying)) + { + m_log.DebugFormat( + "[SCENE PRESENCE]: Aborting CompleteMovement call for {0} in {1} as they are already root", + Name, Scene.Name); + + return; + } // Tell the client that we're totally ready ControllingClient.MoveAgentIntoRegion(m_scene.RegionInfo, AbsolutePosition, look); diff --git a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs index d1aeaee..1ff1329 100644 --- a/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs +++ b/OpenSim/Region/Framework/Scenes/Tests/ScenePresenceAgentTests.cs @@ -111,6 +111,45 @@ namespace OpenSim.Region.Framework.Scenes.Tests Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); } + /// + /// Test that duplicate complete movement calls are ignored. + /// + /// + /// If duplicate calls are not ignored then there is a risk of race conditions or other unexpected effects. + /// + [Test] + public void TestDupeCompleteMovementCalls() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + TestScene scene = new SceneHelpers().SetupScene(); + + int makeRootAgentEvents = 0; + scene.EventManager.OnMakeRootAgent += spi => makeRootAgentEvents++; + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, spUuid); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Normally these would be invoked by a CompleteMovement message coming in to the UDP stack. But for + // convenience, here we will invoke it manually. + sp.CompleteMovement(sp.ControllingClient, true); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Check rest of exepcted parameters. + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(spUuid), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + Assert.That(sp.IsChildAgent, Is.False); + Assert.That(sp.UUID, Is.EqualTo(spUuid)); + + Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); + } + [Test] public void TestCreateDuplicateRootScenePresence() { @@ -249,58 +288,5 @@ namespace OpenSim.Region.Framework.Scenes.Tests // Assert.That(childPresence, Is.Not.Null); // Assert.That(childPresence.IsChildAgent, Is.True); } - -// /// -// /// Test adding a root agent to a scene. Doesn't yet actually complete crossing the agent into the scene. -// /// -// [Test] -// public void T010_TestAddRootAgent() -// { -// TestHelpers.InMethod(); -// -// string firstName = "testfirstname"; -// -// AgentCircuitData agent = new AgentCircuitData(); -// agent.AgentID = agent1; -// agent.firstname = firstName; -// agent.lastname = "testlastname"; -// agent.SessionID = UUID.Random(); -// agent.SecureSessionID = UUID.Random(); -// agent.circuitcode = 123; -// agent.BaseFolder = UUID.Zero; -// agent.InventoryFolder = UUID.Zero; -// agent.startpos = Vector3.Zero; -// agent.CapsPath = GetRandomCapsObjectPath(); -// agent.ChildrenCapSeeds = new Dictionary(); -// agent.child = true; -// -// scene.PresenceService.LoginAgent(agent.AgentID.ToString(), agent.SessionID, agent.SecureSessionID); -// -// string reason; -// scene.NewUserConnection(agent, (uint)TeleportFlags.ViaLogin, out reason); -// testclient = new TestClient(agent, scene); -// scene.AddNewAgent(testclient); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// -// Assert.That(presence, Is.Not.Null, "presence is null"); -// Assert.That(presence.Firstname, Is.EqualTo(firstName), "First name not same"); -// acd1 = agent; -// } -// -// /// -// /// Test removing an uncrossed root agent from a scene. -// /// -// [Test] -// public void T011_TestRemoveRootAgent() -// { -// TestHelpers.InMethod(); -// -// scene.RemoveClient(agent1); -// -// ScenePresence presence = scene.GetScenePresence(agent1); -// -// Assert.That(presence, Is.Null, "presence is not null"); -// } } } \ No newline at end of file -- cgit v1.1 From d88c9756a778bda8b71cc772746df962825968e4 Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Thu, 16 Jan 2014 23:31:50 +0000 Subject: Actually put IsChildAgent = true inside the lock, otherwise there is still a small window for race conditions on duplicate CompleteMovement calls --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'OpenSim/Region/Framework/Scenes') diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index d7511d3..c2554d8 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -932,10 +932,12 @@ namespace OpenSim.Region.Framework.Scenes //m_log.DebugFormat("[SCENE]: known regions in {0}: {1}", Scene.RegionInfo.RegionName, KnownChildRegionHandles.Count); lock (m_completeMovementLock) + { if (!IsChildAgent) return false; - IsChildAgent = false; + IsChildAgent = false; + } // Must reset this here so that a teleport to a region next to an existing region does not keep the flag // set and prevent the close of the connection on a subsequent re-teleport. -- cgit v1.1 From 27abe040bddc11feb78757467c5e330f4cda840b Mon Sep 17 00:00:00 2001 From: Justin Clark-Casey (justincc) Date: Mon, 20 Jan 2014 19:16:19 +0000 Subject: Stop exceptions being generated on agent connection if a telehub object has been deleted or has no spawn points. --- OpenSim/Region/Framework/Scenes/Scene.cs | 50 ++++++--- .../Framework/Scenes/Tests/SceneTelehubTests.cs | 119 +++++++++++++++++++++ 2 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs (limited to 'OpenSim/Region/Framework/Scenes') diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 7772f94..420c0b5 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -3946,32 +3946,52 @@ namespace OpenSim.Region.Framework.Scenes } } +// m_log.DebugFormat( +// "[SCENE]: Found telehub object {0} for new user connection {1} to {2}", +// RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); + // Honor Estate teleport routing via Telehubs excluding ViaHome and GodLike TeleportFlags if (RegionInfo.RegionSettings.TelehubObject != UUID.Zero && RegionInfo.EstateSettings.AllowDirectTeleport == false && !viahome && !godlike) { SceneObjectGroup telehub = GetSceneObjectGroup(RegionInfo.RegionSettings.TelehubObject); - // Can have multiple SpawnPoints - List spawnpoints = RegionInfo.RegionSettings.SpawnPoints(); - if (spawnpoints.Count > 1) + + if (telehub != null) { - // We have multiple SpawnPoints, Route the agent to a random or sequential one - if (SpawnPointRouting == "random") - acd.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( - telehub.AbsolutePosition, - telehub.GroupRotation - ); + // Can have multiple SpawnPoints + List spawnpoints = RegionInfo.RegionSettings.SpawnPoints(); + if (spawnpoints.Count > 1) + { + // We have multiple SpawnPoints, Route the agent to a random or sequential one + if (SpawnPointRouting == "random") + acd.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( + telehub.AbsolutePosition, + telehub.GroupRotation + ); + else + acd.startpos = spawnpoints[SpawnPoint()].GetLocation( + telehub.AbsolutePosition, + telehub.GroupRotation + ); + } + else if (spawnpoints.Count == 1) + { + // We have a single SpawnPoint and will route the agent to it + acd.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + } else - acd.startpos = spawnpoints[SpawnPoint()].GetLocation( - telehub.AbsolutePosition, - telehub.GroupRotation - ); + { + m_log.DebugFormat( + "[SCENE]: No spawnpoints defined for telehub {0} for {1} in {2}. Continuing.", + RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); + } } else { - // We have a single SpawnPoint and will route the agent to it - acd.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + m_log.DebugFormat( + "[SCENE]: No telehub {0} found to direct {1} in {2}. Continuing.", + RegionInfo.RegionSettings.TelehubObject, acd.Name, Name); } return true; diff --git a/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs b/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs new file mode 100644 index 0000000..9a97acc --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/Tests/SceneTelehubTests.cs @@ -0,0 +1,119 @@ +/* + * 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 Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Estate; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using OpenSim.Tests.Common.Mock; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene telehub tests + /// + /// + /// TODO: Tests which run through normal functionality. Currently, the only test is one that checks behaviour + /// in the case of an error condition + /// + [TestFixture] + public class SceneTelehubTests : OpenSimTestCase + { + /// + /// Test for desired behaviour when a telehub has no spawn points + /// + [Test] + public void TestNoTelehubSpawnPoints() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + + /// + /// Test for desired behaviour when the scene object nominated as a telehub object does not exist. + /// + [Test] + public void TestNoTelehubSceneObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + SceneObjectGroup spawnPointSo = SceneHelpers.AddSceneObject(scene, "spawnpointObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "spawnpoint add", spawnPointSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + scene.DeleteSceneObject(telehubSo, false); + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + } +} \ No newline at end of file -- cgit v1.1