From 5e4d6cab00cb29cd088ab7b62ab13aff103b64cb Mon Sep 17 00:00:00 2001 From: onefang Date: Sun, 19 May 2019 21:24:15 +1000 Subject: Dump OpenSim 0.9.0.1 into it's own branch. --- .../ClientStack/Linden/Caps/GetTextureModule.cs | 412 +++++++++++++++++++-- 1 file changed, 374 insertions(+), 38 deletions(-) (limited to 'OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs') diff --git a/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs index bb932f2..b01c7dc 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs @@ -27,18 +27,13 @@ using System; using System.Collections; -using System.Collections.Specialized; -using System.Drawing; -using System.Drawing.Imaging; +using System.Collections.Generic; using System.Reflection; -using System.IO; -using System.Web; +using System.Threading; using log4net; using Nini.Config; using Mono.Addins; using OpenMetaverse; -using OpenMetaverse.StructuredData; -using OpenMetaverse.Imaging; using OpenSim.Framework; using OpenSim.Framework.Servers; using OpenSim.Framework.Servers.HttpServer; @@ -47,6 +42,7 @@ using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using Caps = OpenSim.Framework.Capabilities.Caps; using OpenSim.Capabilities.Handlers; +using OpenSim.Framework.Monitoring; namespace OpenSim.Region.ClientStack.Linden { @@ -54,27 +50,49 @@ namespace OpenSim.Region.ClientStack.Linden [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GetTextureModule")] public class GetTextureModule : INonSharedRegionModule { -// private static readonly ILog m_log = -// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + + struct aPollRequest + { + public PollServiceTextureEventArgs thepoll; + public UUID reqID; + public Hashtable request; + public bool send503; + } + + public class aPollResponse + { + public Hashtable response; + public int bytes; + } + + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene; - private IAssetService m_assetService; - private bool m_Enabled = false; + private static GetTextureHandler m_getTextureHandler; + + private IAssetService m_assetService = null; + + private Dictionary m_capsDict = new Dictionary(); + private static Thread[] m_workerThreads = null; + private static int m_NumberScenes = 0; + private static OpenSim.Framework.BlockingQueue m_queue = + new OpenSim.Framework.BlockingQueue(); - // TODO: Change this to a config option - private string m_RedirectURL = null; + private Dictionary m_pollservices = new Dictionary(); - private string m_URL; + private string m_Url = "localhost"; #region ISharedRegionModule Members public void Initialise(IConfigSource source) { IConfig config = source.Configs["ClientStack.LindenCaps"]; + if (config == null) return; - +/* m_URL = config.GetString("Cap_GetTexture", string.Empty); // Cap doesn't exist if (m_URL != string.Empty) @@ -82,39 +100,108 @@ namespace OpenSim.Region.ClientStack.Linden m_Enabled = true; m_RedirectURL = config.GetString("GetTextureRedirectURL"); } +*/ + m_Url = config.GetString("Cap_GetTexture", "localhost"); } public void AddRegion(Scene s) { - if (!m_Enabled) - return; - m_scene = s; + m_assetService = s.AssetService; } public void RemoveRegion(Scene s) { - if (!m_Enabled) - return; - m_scene.EventManager.OnRegisterCaps -= RegisterCaps; + m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps; + m_scene.EventManager.OnThrottleUpdate -= ThrottleUpdate; + m_NumberScenes--; m_scene = null; } public void RegionLoaded(Scene s) { - if (!m_Enabled) - return; + // We'll reuse the same handler for all requests. + m_getTextureHandler = new GetTextureHandler(m_assetService); - m_assetService = m_scene.RequestModuleInterface(); m_scene.EventManager.OnRegisterCaps += RegisterCaps; + m_scene.EventManager.OnDeregisterCaps += DeregisterCaps; + m_scene.EventManager.OnThrottleUpdate += ThrottleUpdate; + + m_NumberScenes++; + + if (m_workerThreads == null) + { + m_workerThreads = new Thread[2]; + + for (uint i = 0; i < 2; i++) + { + m_workerThreads[i] = WorkManager.StartThread(DoTextureRequests, + String.Format("GetTextureWorker{0}", i), + ThreadPriority.Normal, + true, + false, + null, + int.MaxValue); + } + } + } + private int ExtractImageThrottle(byte[] pthrottles) + { + + byte[] adjData; + int pos = 0; + + if (!BitConverter.IsLittleEndian) + { + byte[] newData = new byte[7 * 4]; + Buffer.BlockCopy(pthrottles, 0, newData, 0, 7 * 4); + + for (int i = 0; i < 7; i++) + Array.Reverse(newData, i * 4, 4); + + adjData = newData; + } + else + { + adjData = pthrottles; + } + + pos = pos + 20; + int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); //pos += 4; + //int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); + return texture; + } + + // Now we know when the throttle is changed by the client in the case of a root agent or by a neighbor region in the case of a child agent. + public void ThrottleUpdate(ScenePresence p) + { + byte[] throttles = p.ControllingClient.GetThrottlesPacked(1); + UUID user = p.UUID; + int imagethrottle = ExtractImageThrottle(throttles); + PollServiceTextureEventArgs args; + if (m_pollservices.TryGetValue(user,out args)) + { + args.UpdateThrottle(imagethrottle); + } } public void PostInitialise() { } - public void Close() { } + public void Close() + { + if(m_NumberScenes <= 0 && m_workerThreads != null) + { + m_log.DebugFormat("[GetTextureModule] Closing"); + + foreach (Thread t in m_workerThreads) + Watchdog.AbortThread(t.ManagedThreadId); + + m_queue.Clear(); + } + } public string Name { get { return "GetTextureModule"; } } @@ -125,28 +212,277 @@ namespace OpenSim.Region.ClientStack.Linden #endregion - public void RegisterCaps(UUID agentID, Caps caps) + private class PollServiceTextureEventArgs : PollServiceEventArgs { - UUID capID = UUID.Random(); + private List requests = + new List(); + private Dictionary responses = + new Dictionary(); + + private Scene m_scene; + private CapsDataThrottler m_throttler = new CapsDataThrottler(100000); + public PollServiceTextureEventArgs(UUID pId, Scene scene) : + base(null, "", null, null, null, pId, int.MaxValue) + { + m_scene = scene; + // x is request id, y is userid + HasEvents = (x, y) => + { + lock (responses) + { + bool ret = m_throttler.hasEvents(x, responses); + return ret; + + } + }; + GetEvents = (x, y) => + { + lock (responses) + { + try + { + return responses[x].response; + } + finally + { + responses.Remove(x); + m_throttler.PassTime(); + } + } + }; + // x is request id, y is request data hashtable + Request = (x, y) => + { + aPollRequest reqinfo = new aPollRequest(); + reqinfo.thepoll = this; + reqinfo.reqID = x; + reqinfo.request = y; + reqinfo.send503 = false; + + lock (responses) + { + if (responses.Count > 0) + { + if (m_queue.Count() >= 4) + { + // Never allow more than 4 fetches to wait + reqinfo.send503 = true; + } + } + } + m_queue.Enqueue(reqinfo); + m_throttler.PassTime(); + }; + + // this should never happen except possible on shutdown + NoEvents = (x, y) => + { +/* + lock (requests) + { + Hashtable request = requests.Find(id => id["RequestID"].ToString() == x.ToString()); + requests.Remove(request); + } +*/ + Hashtable response = new Hashtable(); - //caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture)); - if (m_URL == "localhost") + response["int_response_code"] = 500; + response["str_response_string"] = "Script timeout"; + response["content_type"] = "text/plain"; + response["keepalive"] = false; + response["reusecontext"] = false; + + return response; + }; + } + + public void Process(aPollRequest requestinfo) { -// m_log.DebugFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName); - caps.RegisterHandler( - "GetTexture", - new GetTextureHandler("/CAPS/" + capID + "/", m_assetService, "GetTexture", agentID.ToString(), m_RedirectURL)); + Hashtable response; + + UUID requestID = requestinfo.reqID; + + if(m_scene.ShuttingDown) + return; + + if (requestinfo.send503) + { + response = new Hashtable(); + + response["int_response_code"] = 503; + response["str_response_string"] = "Throttled"; + response["content_type"] = "text/plain"; + response["keepalive"] = false; + response["reusecontext"] = false; + + Hashtable headers = new Hashtable(); + headers["Retry-After"] = 30; + response["headers"] = headers; + + lock (responses) + responses[requestID] = new aPollResponse() {bytes = 0, response = response}; + + return; + } + + // If the avatar is gone, don't bother to get the texture + if (m_scene.GetScenePresence(Id) == null) + { + response = new Hashtable(); + + response["int_response_code"] = 500; + response["str_response_string"] = "Script timeout"; + response["content_type"] = "text/plain"; + response["keepalive"] = false; + response["reusecontext"] = false; + + lock (responses) + responses[requestID] = new aPollResponse() {bytes = 0, response = response}; + + return; + } + + response = m_getTextureHandler.Handle(requestinfo.request); + lock (responses) + { + responses[requestID] = new aPollResponse() + { + bytes = (int) response["int_bytes"], + response = response + }; + + } + m_throttler.PassTime(); } - else + + internal void UpdateThrottle(int pimagethrottle) { -// m_log.DebugFormat("[GETTEXTURE]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); + int tmp = 2 * pimagethrottle; + if(tmp < 10000) + tmp = 10000; + m_throttler.ThrottleBytes = tmp; + } + } + + private void RegisterCaps(UUID agentID, Caps caps) + { + if (m_Url == "localhost") + { + string capUrl = "/CAPS/" + UUID.Random() + "/"; + + // Register this as a poll service + PollServiceTextureEventArgs args = new PollServiceTextureEventArgs(agentID, m_scene); + + args.Type = PollServiceEventArgs.EventType.Texture; + MainServer.Instance.AddPollServiceHTTPHandler(capUrl, args); + + string hostName = m_scene.RegionInfo.ExternalHostName; + uint port = (MainServer.Instance == null) ? 0 : MainServer.Instance.Port; + string protocol = "http"; + + if (MainServer.Instance.UseSSL) + { + hostName = MainServer.Instance.SSLCommonName; + port = MainServer.Instance.SSLPort; + protocol = "https"; + } IExternalCapsModule handler = m_scene.RequestModuleInterface(); if (handler != null) - handler.RegisterExternalUserCapsHandler(agentID,caps,"GetTexture", m_URL); + handler.RegisterExternalUserCapsHandler(agentID, caps, "GetTexture", capUrl); else - caps.RegisterHandler("GetTexture", m_URL); + caps.RegisterHandler("GetTexture", String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, capUrl)); + m_pollservices[agentID] = args; + m_capsDict[agentID] = capUrl; + } + else + { + caps.RegisterHandler("GetTexture", m_Url); + } + } + + private void DeregisterCaps(UUID agentID, Caps caps) + { + PollServiceTextureEventArgs args; + + MainServer.Instance.RemoveHTTPHandler("", m_Url); + m_capsDict.Remove(agentID); + + if (m_pollservices.TryGetValue(agentID, out args)) + { + m_pollservices.Remove(agentID); + } + } + + private static void DoTextureRequests() + { + while (true) + { + aPollRequest poolreq = m_queue.Dequeue(4500); + Watchdog.UpdateThread(); + if(m_NumberScenes <= 0) + return; + if(poolreq.reqID != UUID.Zero) + poolreq.thepoll.Process(poolreq); } } + internal sealed class CapsDataThrottler + { + private double lastTimeElapsed = 0; + private volatile int BytesSent = 0; + public CapsDataThrottler(int pBytes) + { + if(pBytes < 10000) + pBytes = 10000; + ThrottleBytes = pBytes; + lastTimeElapsed = Util.GetTimeStampMS(); + } + public bool hasEvents(UUID key, Dictionary responses) + { + PassTime(); + // Note, this is called IN LOCK + bool haskey = responses.ContainsKey(key); + if (!haskey) + { + return false; + } + GetTextureModule.aPollResponse response; + if (responses.TryGetValue(key, out response)) + { + // This is any error response + if (response.bytes == 0) + return true; + + // Normal + if (BytesSent <= ThrottleBytes) + { + BytesSent += response.bytes; + return true; + } + else + { + return false; + } + } + + return haskey; + } + + public void PassTime() + { + double currenttime = Util.GetTimeStampMS(); + double timeElapsed = currenttime - lastTimeElapsed; + if(timeElapsed < 50.0) + return; + int add = (int)(ThrottleBytes * timeElapsed * 0.001); + if (add >= 1000) + { + lastTimeElapsed = currenttime; + BytesSent -= add; + if (BytesSent < 0) BytesSent = 0; + } + } + public int ThrottleBytes; + } } } -- cgit v1.1