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<MapRequestState> requests = new OpenSim.Framework.BlockingQueue<MapRequestState>();
+
         //private IConfig m_config;
         private Scene m_scene;
         private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>();
@@ -67,7 +70,11 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
         private byte[] myMapImageJPEG;
         private bool m_Enabled = false;
         private Dictionary<UUID, MapRequestState> m_openRequests = new Dictionary<UUID, MapRequestState>();
-        
+        private Dictionary<string, int> m_blacklistedurls = new Dictionary<string, int>();
+        private Dictionary<ulong, int> m_blacklistedregions = new Dictionary<ulong, int>();
+        private Dictionary<ulong, string> m_cachedRegionMapItemsAddress = new Dictionary<ulong, string>();
+        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
 
-
+        /// <summary>
+        /// Registered for event
+        /// </summary>
+        /// <param name="client"></param>
         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;            
         }
+
+        /// <summary>
+        /// Client logged out, check to see if there are any more root agents in the simulator
+        /// If not, stop the mapItemRequest Thread
+        /// Event handler
+        /// </summary>
+        /// <param name="AgentId">AgentID that logged out</param>
         private void ClientLoggedOut(UUID AgentId)
         {
+          
+            List<ScenePresence> 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();
+
 
         }
         #endregion
 
+        /// <summary>
+        /// 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.
+        /// </summary>
+        /// <param name="o"></param>
+        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);
+        }
+
+        /// <summary>
+        /// Enqueues a 'stop thread' MapRequestState.  Causes the MapItemRequest thread to end
+        /// </summary>
+        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<ScenePresence> avatars = m_scene.GetAvatars();
                     int tc = System.Environment.TickCount;
                     List<mapItemReply> mapitems = new List<mapItemReply>();
@@ -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);
+        /// <summary>
+        /// Processing thread main() loop for doing remote mapitem requests
+        /// </summary>
+        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)
+        /// <summary>
+        /// Enqueues the map item request into the processing thread
+        /// </summary>
+        /// <param name="state"></param>
+        public void EnqueueMapItemRequest(MapRequestState state)
         {
-            
-            RequestMapItemsDelegate icon = (RequestMapItemsDelegate)iar.AsyncState;
-            LLSDMap response = icon.EndInvoke(iar);
-            
+            requests.Enqueue(state);
+        }
 
+        /// <summary>
+        /// Sends the mapitem response to the IClientAPI
+        /// </summary>
+        /// <param name="response">The LLSDMap Response for the mapitem</param>
+        private void RequestMapItemsCompleted(LLSDMap response)
+        {
 
             UUID requestID = response["requestID"].AsUUID();
 
@@ -364,19 +455,99 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
                 }
             }
         }
+
+        /// <summary>
+        /// Enqueue the MapItem request for remote processing
+        /// </summary>
+        /// <param name="httpserver">blank string, we discover this in the process</param>
+        /// <param name="id">Agent ID that we are making this request on behalf</param>
+        /// <param name="flags">passed in from packet</param>
+        /// <param name="EstateID">passed in from packet</param>
+        /// <param name="godlike">passed in from packet</param>
+        /// <param name="itemtype">passed in from packet</param>
+        /// <param name="regionhandle">Region we're looking up</param>
         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);
+           
         }
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        /// <param name="httpserver">blank string, we discover this in the process</param>
+        /// <param name="id">Agent ID that we are making this request on behalf</param>
+        /// <param name="flags">passed in from packet</param>
+        /// <param name="EstateID">passed in from packet</param>
+        /// <param name="godlike">passed in from packet</param>
+        /// <param name="itemtype">passed in from packet</param>
+        /// <param name="regionhandle">Region we're looking up</param>
+        /// <returns></returns>
         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<ScenePresence> 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