From 2c5497fa3ae7c132b9c8c3022d65fbd9efab2844 Mon Sep 17 00:00:00 2001 From: Teravus Ovares Date: Wed, 8 Oct 2008 11:53:35 +0000 Subject: * Re-enables map item requests. * Puts remote requests in a single worker thread * Worker thread only starts when there are agents to serve * When there are no agents to serve, it shuts down * A good example of how to deal with threads in non-shared modules so they don't end up consuming threads per regions --- .../Modules/World/WorldMap/WorldMapModule.cs | 312 +++++++++++++++++---- 1 file changed, 261 insertions(+), 51 deletions(-) diff --git a/OpenSim/Region/Environment/Modules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/Environment/Modules/World/WorldMap/WorldMapModule.cs index 215f3fa..9b4a997 100644 --- a/OpenSim/Region/Environment/Modules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/Environment/Modules/World/WorldMap/WorldMapModule.cs @@ -33,6 +33,7 @@ using System.Drawing.Imaging; using System.IO; using System.Net; using System.Reflection; +using System.Threading; using OpenMetaverse; using OpenMetaverse.Imaging; using OpenMetaverse.StructuredData; @@ -60,6 +61,8 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap private static readonly string m_mapLayerPath = "0001/"; + private OpenSim.Framework.BlockingQueue requests = new OpenSim.Framework.BlockingQueue(); + //private IConfig m_config; private Scene m_scene; private List cachedMapBlocks = new List(); @@ -67,7 +70,11 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap private byte[] myMapImageJPEG; private bool m_Enabled = false; private Dictionary m_openRequests = new Dictionary(); - + private Dictionary m_blacklistedurls = new Dictionary(); + private Dictionary m_blacklistedregions = new Dictionary(); + private Dictionary m_cachedRegionMapItemsAddress = new Dictionary(); + private Thread mapItemReqThread; + private volatile bool threadrunning = false; //private int CacheRegionsDistance = 256; @@ -93,12 +100,15 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap m_scene.AddHTTPHandler(regionimage, OnHTTPGetMapImage); m_scene.AddLLSDHandler("/MAP/MapItems/" + scene.RegionInfo.RegionHandle.ToString(),HandleRemoteMapItemRequest); - //QuadTree.Subdivide(); - //QuadTree.Subdivide(); + scene.EventManager.OnRegisterCaps += OnRegisterCaps; scene.EventManager.OnNewClient += OnNewClient; scene.EventManager.OnClientClosed += ClientLoggedOut; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + + } public void PostInitialise() { @@ -220,33 +230,80 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap } #region EventHandlers - + /// + /// Registered for event + /// + /// private void OnNewClient(IClientAPI client) { - // All friends establishment protocol goes over instant message - // There's no way to send a message from the sim - // to a user to 'add a friend' without causing dialog box spam - // - // The base set of friends are added when the user signs on in their XMLRPC response - // Generated by LoginService. The friends are retreived from the database by the UserManager - - // Subscribe to instant messages - - //client.OnInstantMessage += OnInstantMessage; - //client.OnApproveFriendRequest += OnApprovedFriendRequest; - //client.OnDenyFriendRequest += OnDenyFriendRequest; - //client.OnTerminateFriendship += OnTerminateFriendship; - - //doFriendListUpdateOnline(client.AgentId); client.OnRequestMapBlocks += RequestMapBlocks; - client.OnMapItemRequest += HandleMapItemRequest; + client.OnMapItemRequest += HandleMapItemRequest; } + + /// + /// Client logged out, check to see if there are any more root agents in the simulator + /// If not, stop the mapItemRequest Thread + /// Event handler + /// + /// AgentID that logged out private void ClientLoggedOut(UUID AgentId) { + + List presences = m_scene.GetAvatars(); + int rootcount = 0; + for (int i=0;i + /// Starts the MapItemRequest Thread + /// Note that this only gets started when there are actually agents in the region + /// Additionally, it gets stopped when there are none. + /// + /// + private void StartThread(object o) + { + if (threadrunning) return; + + m_log.Warn("[WorldMap]: Starting remote MapItem request thread"); + threadrunning = true; + mapItemReqThread = new Thread(new ThreadStart(process)); + mapItemReqThread.IsBackground = true; + mapItemReqThread.Name = "MapItemRequestThread"; + mapItemReqThread.Priority = ThreadPriority.BelowNormal; + mapItemReqThread.SetApartmentState(ApartmentState.MTA); + mapItemReqThread.Start(); + ThreadTracker.Add(mapItemReqThread); + } + + /// + /// Enqueues a 'stop thread' MapRequestState. Causes the MapItemRequest thread to end + /// + private void StopThread() + { + MapRequestState st = new MapRequestState(); + st.agentID=UUID.Zero; + st.EstateID=0; + st.flags=0; + st.godlike=false; + st.itemtype=0; + st.regionhandle=0; + + requests.Enqueue(st); + } + + public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags, uint EstateID, bool godlike, uint itemtype, ulong regionhandle) { @@ -257,6 +314,7 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap { if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) { + // Local Map Item Request List avatars = m_scene.GetAvatars(); int tc = System.Environment.TickCount; List mapitems = new List(); @@ -295,29 +353,62 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap } else { - //RegionInfo mreg = m_scene.SceneGridService.RequestNeighbouringRegionInfo(regionhandle); - //if (mreg != null) - //{ - // string httpserver = "http://" + mreg.ExternalEndPoint.Address.ToString() + ":" + mreg.HttpPort + "/MAP/MapItems/" + regionhandle.ToString(); - - // RequestMapItems(httpserver,remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); - - //} + // Remote Map Item Request + + + // ensures that the blockingqueue doesn't get borked if the GetAgents() timing changes. + // Note that we only start up a remote mapItem Request thread if there's users who could + // be making requests + if (!threadrunning) + { + m_log.Warn("[WorldMap]: Starting new remote request thread manually. This means that AvatarEnteringParcel never fired! This needs to be fixed! Don't Mantis this, as the developers can see it in this message"); + StartThread(new object()); + } + + RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); + } } } - public delegate LLSDMap RequestMapItemsDelegate(string httpserver, UUID id, uint flags, - uint EstateID, bool godlike, uint itemtype, ulong regionhandle); + /// + /// Processing thread main() loop for doing remote mapitem requests + /// + public void process() + { + while (true) + { + MapRequestState st = requests.Dequeue(); + + // end gracefully + if (st.agentID == UUID.Zero) + { + ThreadTracker.Remove(mapItemReqThread); + break; + } + LLSDMap response = RequestMapItemsAsync("", st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle); + RequestMapItemsCompleted(response); + } + threadrunning = false; + m_log.Warn("[WorldMap]: Remote request thread exiting"); + } - private void RequestMapItemsCompleted(IAsyncResult iar) + /// + /// Enqueues the map item request into the processing thread + /// + /// + public void EnqueueMapItemRequest(MapRequestState state) { - - RequestMapItemsDelegate icon = (RequestMapItemsDelegate)iar.AsyncState; - LLSDMap response = icon.EndInvoke(iar); - + requests.Enqueue(state); + } + /// + /// Sends the mapitem response to the IClientAPI + /// + /// The LLSDMap Response for the mapitem + private void RequestMapItemsCompleted(LLSDMap response) + { UUID requestID = response["requestID"].AsUUID(); @@ -364,19 +455,99 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap } } } + + /// + /// Enqueue the MapItem request for remote processing + /// + /// blank string, we discover this in the process + /// Agent ID that we are making this request on behalf + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// Region we're looking up public void RequestMapItems(string httpserver, UUID id, uint flags, uint EstateID, bool godlike, uint itemtype, ulong regionhandle) { - //m_log.Info("[INTER]: " + debugRegionName + ": SceneCommunicationService: Sending InterRegion Notification that region is up " + region.RegionName); - RequestMapItemsDelegate d = RequestMapItemsAsync; - d.BeginInvoke(httpserver, id,flags,EstateID,godlike,itemtype,regionhandle,RequestMapItemsCompleted, d); - //bool val = m_commsProvider.InterRegion.RegionUp(new SerializableRegionInfo(region)); + MapRequestState st = new MapRequestState(); + st.agentID = id; + st.flags = flags; + st.EstateID = EstateID; + st.godlike = godlike; + st.itemtype = itemtype; + st.regionhandle = regionhandle; + EnqueueMapItemRequest(st); + } + + /// + /// Does the actual remote mapitem request + /// This should be called from an asynchronous thread + /// Request failures get blacklisted until region restart so we don't + /// continue to spend resources trying to contact regions that are down. + /// + /// blank string, we discover this in the process + /// Agent ID that we are making this request on behalf + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// Region we're looking up + /// private LLSDMap RequestMapItemsAsync(string httpserver, UUID id, uint flags, uint EstateID, bool godlike, uint itemtype, ulong regionhandle) { + bool blacklisted = false; + lock (m_blacklistedregions) + { + if (m_blacklistedregions.ContainsKey(regionhandle)) + blacklisted = true; + + } + + if (blacklisted) + return new LLSDMap(); + UUID requestID = UUID.Random(); - + lock (m_cachedRegionMapItemsAddress) + { + if (m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) + httpserver = m_cachedRegionMapItemsAddress[regionhandle]; + } + if (httpserver.Length == 0) + { + RegionInfo mreg = m_scene.SceneGridService.RequestNeighbouringRegionInfo(regionhandle); + if (mreg != null) + { + httpserver = "http://" + mreg.ExternalEndPoint.Address.ToString() + ":" + mreg.HttpPort + "/MAP/MapItems/" + regionhandle.ToString(); + lock (m_cachedRegionMapItemsAddress) + { + if (!m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) + m_cachedRegionMapItemsAddress.Add(regionhandle, httpserver); + } + } + else + { + lock (m_blacklistedregions) + { + if (!m_blacklistedregions.ContainsKey(regionhandle)) + m_blacklistedregions.Add(regionhandle, System.Environment.TickCount); + } + m_log.WarnFormat("[WorldMap]: Blacklisted region {0}", regionhandle.ToString()); + } + } + + blacklisted = false; + lock (m_blacklistedurls) + { + if (m_blacklistedurls.ContainsKey(httpserver)) + blacklisted = true; + } + + // Can't find the http server + if (httpserver.Length == 0 || blacklisted) + return new LLSDMap(); + MapRequestState mrs = new MapRequestState(); mrs.agentID = id; mrs.EstateID = EstateID; @@ -413,30 +584,43 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap { m_log.InfoFormat("[WorldMap] Bad send on GetMapItems {0}", ex.Message); responseMap["connect"] = LLSD.FromBoolean(false); + lock (m_blacklistedurls) + { + if (!m_blacklistedurls.ContainsKey(httpserver)) + m_blacklistedurls.Add(httpserver, System.Environment.TickCount); + } + + m_log.WarnFormat("[WorldMap]: Blacklisted {0}", httpserver); return responseMap; } - //m_log.Info("[OGP] waiting for a reply after rez avatar send"); string response_mapItems_reply = null; { // get the response try { WebResponse webResponse = mapitemsrequest.GetResponse(); - if (webResponse == null) + if (webResponse != null) { - //m_log.Info("[OGP:] Null reply on rez_avatar post"); + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + response_mapItems_reply = sr.ReadToEnd().Trim(); + } + else + { + return new LLSDMap(); } - - StreamReader sr = new StreamReader(webResponse.GetResponseStream()); - response_mapItems_reply = sr.ReadToEnd().Trim(); - //m_log.InfoFormat("[OGP]: rez_avatar reply was {0} ", response_mapItems_reply); - } catch (WebException) { - //m_log.InfoFormat("[OGP]: exception on read after send of rez avatar {0}", ex.Message); + responseMap["connect"] = LLSD.FromBoolean(false); + lock (m_blacklistedurls) + { + if (!m_blacklistedurls.ContainsKey(httpserver)) + m_blacklistedurls.Add(httpserver, System.Environment.TickCount); + } + + m_log.WarnFormat("[WorldMap]: Blacklisted {0}", httpserver); return responseMap; } @@ -655,7 +839,34 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap } return responsemap; } + + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) + { + // You may ask, why this is in a threadpool to start with.. + // The reason is so we don't cause the thread to freeze waiting + // for the 1 second it costs to start a thread manually. + + if (!threadrunning) + ThreadPool.QueueUserWorkItem(new WaitCallback(this.StartThread)); + } + + private void MakeChildAgent(ScenePresence avatar) + { + List presences = m_scene.GetAvatars(); + int rootcount = 0; + for (int i = 0; i < presences.Count; i++) + { + if (presences[i] != null) + { + if (!presences[i].IsChildAgent) + rootcount++; + } + } + if (rootcount <= 1) + StopThread(); + } } + public struct MapRequestState { public UUID agentID; @@ -665,6 +876,5 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap public uint itemtype; public ulong regionhandle; } - - + } -- cgit v1.1