* 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.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);
private void RequestMapItemsCompleted(IAsyncResult iar)
/// <summary>
/// Processing thread main() loop for doing remote mapitem requests
/// </summary>
public void process()
{
RequestMapItemsDelegate icon = (RequestMapItemsDelegate)iar.AsyncState;
LLSDMap response = icon.EndInvoke(iar);
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();
@ -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;
}
}