* 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
0.6.0-stable
Teravus Ovares 2008-10-08 11:53:35 +00:00
parent 2947ef9c00
commit 2c5497fa3a
1 changed files with 262 additions and 52 deletions

View File

@ -33,6 +33,7 @@ using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
using System.Threading;
using OpenMetaverse; using OpenMetaverse;
using OpenMetaverse.Imaging; using OpenMetaverse.Imaging;
using OpenMetaverse.StructuredData; using OpenMetaverse.StructuredData;
@ -60,6 +61,8 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
private static readonly string m_mapLayerPath = "0001/"; private static readonly string m_mapLayerPath = "0001/";
private OpenSim.Framework.BlockingQueue<MapRequestState> requests = new OpenSim.Framework.BlockingQueue<MapRequestState>();
//private IConfig m_config; //private IConfig m_config;
private Scene m_scene; private Scene m_scene;
private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>(); private List<MapBlockData> cachedMapBlocks = new List<MapBlockData>();
@ -67,7 +70,11 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
private byte[] myMapImageJPEG; private byte[] myMapImageJPEG;
private bool m_Enabled = false; private bool m_Enabled = false;
private Dictionary<UUID, MapRequestState> m_openRequests = new Dictionary<UUID, MapRequestState>(); 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; //private int CacheRegionsDistance = 256;
@ -93,12 +100,15 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
m_scene.AddHTTPHandler(regionimage, OnHTTPGetMapImage); m_scene.AddHTTPHandler(regionimage, OnHTTPGetMapImage);
m_scene.AddLLSDHandler("/MAP/MapItems/" + scene.RegionInfo.RegionHandle.ToString(),HandleRemoteMapItemRequest); m_scene.AddLLSDHandler("/MAP/MapItems/" + scene.RegionInfo.RegionHandle.ToString(),HandleRemoteMapItemRequest);
//QuadTree.Subdivide();
//QuadTree.Subdivide();
scene.EventManager.OnRegisterCaps += OnRegisterCaps; scene.EventManager.OnRegisterCaps += OnRegisterCaps;
scene.EventManager.OnNewClient += OnNewClient; scene.EventManager.OnNewClient += OnNewClient;
scene.EventManager.OnClientClosed += ClientLoggedOut; scene.EventManager.OnClientClosed += ClientLoggedOut;
scene.EventManager.OnMakeChildAgent += MakeChildAgent;
scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel;
} }
public void PostInitialise() public void PostInitialise()
{ {
@ -220,33 +230,80 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
} }
#region EventHandlers #region EventHandlers
/// <summary>
/// Registered for event
/// </summary>
/// <param name="client"></param>
private void OnNewClient(IClientAPI client) 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.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) 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 #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, public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags,
uint EstateID, bool godlike, uint itemtype, ulong regionhandle) 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) if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle)
{ {
// Local Map Item Request
List<ScenePresence> avatars = m_scene.GetAvatars(); List<ScenePresence> avatars = m_scene.GetAvatars();
int tc = System.Environment.TickCount; int tc = System.Environment.TickCount;
List<mapItemReply> mapitems = new List<mapItemReply>(); List<mapItemReply> mapitems = new List<mapItemReply>();
@ -295,29 +353,62 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
} }
else else
{ {
//RegionInfo mreg = m_scene.SceneGridService.RequestNeighbouringRegionInfo(regionhandle); // Remote Map Item Request
//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);
//} // 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)
}
public delegate LLSDMap RequestMapItemsDelegate(string httpserver, UUID id, uint flags,
uint EstateID, bool godlike, uint itemtype, ulong regionhandle);
private void RequestMapItemsCompleted(IAsyncResult iar)
{ {
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());
}
RequestMapItemsDelegate icon = (RequestMapItemsDelegate)iar.AsyncState; RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle);
LLSDMap response = icon.EndInvoke(iar);
}
}
}
/// <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");
}
/// <summary>
/// Enqueues the map item request into the processing thread
/// </summary>
/// <param name="state"></param>
public void EnqueueMapItemRequest(MapRequestState state)
{
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(); UUID requestID = response["requestID"].AsUUID();
@ -364,18 +455,98 @@ 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, public void RequestMapItems(string httpserver, UUID id, uint flags,
uint EstateID, bool godlike, uint itemtype, ulong regionhandle) uint EstateID, bool godlike, uint itemtype, ulong regionhandle)
{ {
//m_log.Info("[INTER]: " + debugRegionName + ": SceneCommunicationService: Sending InterRegion Notification that region is up " + region.RegionName); MapRequestState st = new MapRequestState();
RequestMapItemsDelegate d = RequestMapItemsAsync; st.agentID = id;
d.BeginInvoke(httpserver, id,flags,EstateID,godlike,itemtype,regionhandle,RequestMapItemsCompleted, d); st.flags = flags;
//bool val = m_commsProvider.InterRegion.RegionUp(new SerializableRegionInfo(region)); 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, private LLSDMap RequestMapItemsAsync(string httpserver, UUID id, uint flags,
uint EstateID, bool godlike, uint itemtype, ulong regionhandle) 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(); 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(); MapRequestState mrs = new MapRequestState();
mrs.agentID = id; mrs.agentID = id;
@ -413,30 +584,43 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
{ {
m_log.InfoFormat("[WorldMap] Bad send on GetMapItems {0}", ex.Message); m_log.InfoFormat("[WorldMap] Bad send on GetMapItems {0}", ex.Message);
responseMap["connect"] = LLSD.FromBoolean(false); 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; return responseMap;
} }
//m_log.Info("[OGP] waiting for a reply after rez avatar send");
string response_mapItems_reply = null; string response_mapItems_reply = null;
{ // get the response { // get the response
try try
{ {
WebResponse webResponse = mapitemsrequest.GetResponse(); 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()); StreamReader sr = new StreamReader(webResponse.GetResponseStream());
response_mapItems_reply = sr.ReadToEnd().Trim(); response_mapItems_reply = sr.ReadToEnd().Trim();
//m_log.InfoFormat("[OGP]: rez_avatar reply was {0} ", response_mapItems_reply); }
else
{
return new LLSDMap();
}
} }
catch (WebException) catch (WebException)
{ {
//m_log.InfoFormat("[OGP]: exception on read after send of rez avatar {0}", ex.Message);
responseMap["connect"] = LLSD.FromBoolean(false); 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; return responseMap;
} }
@ -655,7 +839,34 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
} }
return responsemap; 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 struct MapRequestState
{ {
public UUID agentID; public UUID agentID;
@ -666,5 +877,4 @@ namespace OpenSim.Region.Environment.Modules.World.WorldMap
public ulong regionhandle; public ulong regionhandle;
} }
} }