aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs')
-rw-r--r--OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs424
1 files changed, 382 insertions, 42 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs
index 54cf285..e053054 100644
--- a/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs
+++ b/OpenSim/Region/ClientStack/Linden/Caps/GetTextureModule.cs
@@ -27,18 +27,13 @@
27 27
28using System; 28using System;
29using System.Collections; 29using System.Collections;
30using System.Collections.Specialized; 30using System.Collections.Generic;
31using System.Drawing;
32using System.Drawing.Imaging;
33using System.Reflection; 31using System.Reflection;
34using System.IO; 32using System.Threading;
35using System.Web;
36using log4net; 33using log4net;
37using Nini.Config; 34using Nini.Config;
38using Mono.Addins; 35using Mono.Addins;
39using OpenMetaverse; 36using OpenMetaverse;
40using OpenMetaverse.StructuredData;
41using OpenMetaverse.Imaging;
42using OpenSim.Framework; 37using OpenSim.Framework;
43using OpenSim.Framework.Servers; 38using OpenSim.Framework.Servers;
44using OpenSim.Framework.Servers.HttpServer; 39using OpenSim.Framework.Servers.HttpServer;
@@ -47,6 +42,7 @@ using OpenSim.Region.Framework.Scenes;
47using OpenSim.Services.Interfaces; 42using OpenSim.Services.Interfaces;
48using Caps = OpenSim.Framework.Capabilities.Caps; 43using Caps = OpenSim.Framework.Capabilities.Caps;
49using OpenSim.Capabilities.Handlers; 44using OpenSim.Capabilities.Handlers;
45using OpenSim.Framework.Monitoring;
50 46
51namespace OpenSim.Region.ClientStack.Linden 47namespace OpenSim.Region.ClientStack.Linden
52{ 48{
@@ -54,16 +50,39 @@ namespace OpenSim.Region.ClientStack.Linden
54 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GetTextureModule")] 50 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GetTextureModule")]
55 public class GetTextureModule : INonSharedRegionModule 51 public class GetTextureModule : INonSharedRegionModule
56 { 52 {
57// private static readonly ILog m_log = 53
58// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 54 struct aPollRequest
59 55 {
56 public PollServiceTextureEventArgs thepoll;
57 public UUID reqID;
58 public Hashtable request;
59 public bool send503;
60 }
61
62 public class aPollResponse
63 {
64 public Hashtable response;
65 public int bytes;
66 }
67
68
69 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
70
60 private Scene m_scene; 71 private Scene m_scene;
61 private IAssetService m_assetService;
62 72
63 private bool m_Enabled = false; 73 private static GetTextureHandler m_getTextureHandler;
74
75 private IAssetService m_assetService = null;
76
77 private Dictionary<UUID, string> m_capsDict = new Dictionary<UUID, string>();
78 private static Thread[] m_workerThreads = null;
79
80 private string m_Url = "localhost";
64 81
65 // TODO: Change this to a config option 82 private static OpenMetaverse.BlockingQueue<aPollRequest> m_queue =
66 const string REDIRECT_URL = null; 83 new OpenMetaverse.BlockingQueue<aPollRequest>();
84
85 private Dictionary<UUID,PollServiceTextureEventArgs> m_pollservices = new Dictionary<UUID,PollServiceTextureEventArgs>();
67 86
68 private string m_URL; 87 private string m_URL;
69 88
@@ -72,39 +91,98 @@ namespace OpenSim.Region.ClientStack.Linden
72 public void Initialise(IConfigSource source) 91 public void Initialise(IConfigSource source)
73 { 92 {
74 IConfig config = source.Configs["ClientStack.LindenCaps"]; 93 IConfig config = source.Configs["ClientStack.LindenCaps"];
75 if (config == null) 94 if (config != null)
76 return; 95 m_Url = config.GetString("Cap_GetTexture", "localhost");
77
78 m_URL = config.GetString("Cap_GetTexture", string.Empty);
79 // Cap doesn't exist
80 if (m_URL != string.Empty)
81 m_Enabled = true;
82 } 96 }
83 97
84 public void AddRegion(Scene s) 98 public void AddRegion(Scene s)
85 { 99 {
86 if (!m_Enabled)
87 return;
88
89 m_scene = s; 100 m_scene = s;
101 m_assetService = s.AssetService;
90 } 102 }
91 103
92 public void RemoveRegion(Scene s) 104 public void RemoveRegion(Scene s)
93 { 105 {
94 if (!m_Enabled)
95 return;
96
97 m_scene.EventManager.OnRegisterCaps -= RegisterCaps; 106 m_scene.EventManager.OnRegisterCaps -= RegisterCaps;
107 m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps;
108 m_scene.EventManager.OnThrottleUpdate -= ThrottleUpdate;
98 m_scene = null; 109 m_scene = null;
99 } 110 }
100 111
101 public void RegionLoaded(Scene s) 112 public void RegionLoaded(Scene s)
102 { 113 {
103 if (!m_Enabled) 114 // We'll reuse the same handler for all requests.
104 return; 115 m_getTextureHandler = new GetTextureHandler(m_assetService);
105 116
106 m_assetService = m_scene.RequestModuleInterface<IAssetService>();
107 m_scene.EventManager.OnRegisterCaps += RegisterCaps; 117 m_scene.EventManager.OnRegisterCaps += RegisterCaps;
118 m_scene.EventManager.OnDeregisterCaps += DeregisterCaps;
119 m_scene.EventManager.OnThrottleUpdate += ThrottleUpdate;
120
121 if (m_workerThreads == null)
122 {
123 m_workerThreads = new Thread[2];
124
125 for (uint i = 0; i < 2; i++)
126 {
127 m_workerThreads[i] = Watchdog.StartThread(DoTextureRequests,
128 String.Format("TextureWorkerThread{0}", i),
129 ThreadPriority.Normal,
130 false,
131 false,
132 null,
133 int.MaxValue);
134 }
135 }
136 }
137 private int ExtractImageThrottle(byte[] pthrottles)
138 {
139
140 byte[] adjData;
141 int pos = 0;
142
143 if (!BitConverter.IsLittleEndian)
144 {
145 byte[] newData = new byte[7 * 4];
146 Buffer.BlockCopy(pthrottles, 0, newData, 0, 7 * 4);
147
148 for (int i = 0; i < 7; i++)
149 Array.Reverse(newData, i * 4, 4);
150
151 adjData = newData;
152 }
153 else
154 {
155 adjData = pthrottles;
156 }
157
158 // 0.125f converts from bits to bytes
159 //int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
160 //pos += 4;
161 // int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
162 //pos += 4;
163 // int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
164 // pos += 4;
165 // int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
166 // pos += 4;
167 // int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
168 // pos += 4;
169 pos = pos + 20;
170 int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); //pos += 4;
171 //int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
172 return texture;
173 }
174
175 // 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.
176 public void ThrottleUpdate(ScenePresence p)
177 {
178 byte[] throttles = p.ControllingClient.GetThrottlesPacked(1);
179 UUID user = p.UUID;
180 int imagethrottle = ExtractImageThrottle(throttles);
181 PollServiceTextureEventArgs args;
182 if (m_pollservices.TryGetValue(user,out args))
183 {
184 args.UpdateThrottle(imagethrottle);
185 }
108 } 186 }
109 187
110 public void PostInitialise() 188 public void PostInitialise()
@@ -122,28 +200,290 @@ namespace OpenSim.Region.ClientStack.Linden
122 200
123 #endregion 201 #endregion
124 202
125 public void RegisterCaps(UUID agentID, Caps caps) 203 ~GetTextureModule()
126 { 204 {
127 UUID capID = UUID.Random(); 205 foreach (Thread t in m_workerThreads)
206 Watchdog.AbortThread(t.ManagedThreadId);
128 207
129 //caps.RegisterHandler("GetTexture", new StreamHandler("GET", "/CAPS/" + capID, ProcessGetTexture)); 208 }
130 if (m_URL == "localhost") 209
210 private class PollServiceTextureEventArgs : PollServiceEventArgs
211 {
212 private List<Hashtable> requests =
213 new List<Hashtable>();
214 private Dictionary<UUID, aPollResponse> responses =
215 new Dictionary<UUID, aPollResponse>();
216
217 private Scene m_scene;
218 private CapsDataThrottler m_throttler = new CapsDataThrottler(100000, 1400000,10000);
219 public PollServiceTextureEventArgs(UUID pId, Scene scene) :
220 base(null, "", null, null, null, pId, int.MaxValue)
131 { 221 {
132// m_log.DebugFormat("[GETTEXTURE]: /CAPS/{0} in region {1}", capID, m_scene.RegionInfo.RegionName); 222 m_scene = scene;
133 caps.RegisterHandler( 223 // x is request id, y is userid
134 "GetTexture", 224 HasEvents = (x, y) =>
135 new GetTextureHandler("/CAPS/" + capID + "/", m_assetService, "GetTexture", agentID.ToString())); 225 {
226 lock (responses)
227 {
228 bool ret = m_throttler.hasEvents(x, responses);
229 m_throttler.ProcessTime();
230 return ret;
231
232 }
233 };
234 GetEvents = (x, y) =>
235 {
236 lock (responses)
237 {
238 try
239 {
240 return responses[x].response;
241 }
242 finally
243 {
244 responses.Remove(x);
245 }
246 }
247 };
248 // x is request id, y is request data hashtable
249 Request = (x, y) =>
250 {
251 aPollRequest reqinfo = new aPollRequest();
252 reqinfo.thepoll = this;
253 reqinfo.reqID = x;
254 reqinfo.request = y;
255 reqinfo.send503 = false;
256
257 lock (responses)
258 {
259 if (responses.Count > 0)
260 {
261 if (m_queue.Count >= 4)
262 {
263 // Never allow more than 4 fetches to wait
264 reqinfo.send503 = true;
265 }
266 }
267 }
268 m_queue.Enqueue(reqinfo);
269 };
270
271 // this should never happen except possible on shutdown
272 NoEvents = (x, y) =>
273 {
274/*
275 lock (requests)
276 {
277 Hashtable request = requests.Find(id => id["RequestID"].ToString() == x.ToString());
278 requests.Remove(request);
279 }
280*/
281 Hashtable response = new Hashtable();
282
283 response["int_response_code"] = 500;
284 response["str_response_string"] = "Script timeout";
285 response["content_type"] = "text/plain";
286 response["keepalive"] = false;
287 response["reusecontext"] = false;
288
289 return response;
290 };
136 } 291 }
137 else 292
293 public void Process(aPollRequest requestinfo)
138 { 294 {
139// m_log.DebugFormat("[GETTEXTURE]: {0} in region {1}", m_URL, m_scene.RegionInfo.RegionName); 295 Hashtable response;
296
297 UUID requestID = requestinfo.reqID;
298
299 if (requestinfo.send503)
300 {
301 response = new Hashtable();
302
303 response["int_response_code"] = 503;
304 response["str_response_string"] = "Throttled";
305 response["content_type"] = "text/plain";
306 response["keepalive"] = false;
307 response["reusecontext"] = false;
308
309 lock (responses)
310 responses[requestID] = new aPollResponse() {bytes = 0, response = response};
311
312 return;
313 }
314
315 // If the avatar is gone, don't bother to get the texture
316 if (m_scene.GetScenePresence(Id) == null)
317 {
318 response = new Hashtable();
319
320 response["int_response_code"] = 500;
321 response["str_response_string"] = "Script timeout";
322 response["content_type"] = "text/plain";
323 response["keepalive"] = false;
324 response["reusecontext"] = false;
325
326 lock (responses)
327 responses[requestID] = new aPollResponse() {bytes = 0, response = response};
328
329 return;
330 }
331
332 response = m_getTextureHandler.Handle(requestinfo.request);
333 lock (responses)
334 {
335 responses[requestID] = new aPollResponse()
336 {
337 bytes = (int) response["int_bytes"],
338 response = response
339 };
340
341 }
342 m_throttler.ProcessTime();
343 }
344
345 internal void UpdateThrottle(int pimagethrottle)
346 {
347 m_throttler.ThrottleBytes = pimagethrottle;
348 }
349 }
350
351 private void RegisterCaps(UUID agentID, Caps caps)
352 {
353 if (m_Url == "localhost")
354 {
355 string capUrl = "/CAPS/" + UUID.Random() + "/";
356
357 // Register this as a poll service
358 PollServiceTextureEventArgs args = new PollServiceTextureEventArgs(agentID, m_scene);
359
360 args.Type = PollServiceEventArgs.EventType.Texture;
361 MainServer.Instance.AddPollServiceHTTPHandler(capUrl, args);
362
363 string hostName = m_scene.RegionInfo.ExternalHostName;
364 uint port = (MainServer.Instance == null) ? 0 : MainServer.Instance.Port;
365 string protocol = "http";
366
367 if (MainServer.Instance.UseSSL)
368 {
369 hostName = MainServer.Instance.SSLCommonName;
370 port = MainServer.Instance.SSLPort;
371 protocol = "https";
372 }
140 IExternalCapsModule handler = m_scene.RequestModuleInterface<IExternalCapsModule>(); 373 IExternalCapsModule handler = m_scene.RequestModuleInterface<IExternalCapsModule>();
141 if (handler != null) 374 if (handler != null)
142 handler.RegisterExternalUserCapsHandler(agentID,caps,"GetTexture",m_URL); 375 handler.RegisterExternalUserCapsHandler(agentID, caps, "GetTexture", capUrl);
143 else 376 else
144 caps.RegisterHandler("GetTexture", m_URL); 377 caps.RegisterHandler("GetTexture", String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, capUrl));
378 m_pollservices[agentID] = args;
379 m_capsDict[agentID] = capUrl;
380 }
381 else
382 {
383 caps.RegisterHandler("GetTexture", m_Url);
145 } 384 }
146 } 385 }
147 386
387 private void DeregisterCaps(UUID agentID, Caps caps)
388 {
389 PollServiceTextureEventArgs args;
390
391 MainServer.Instance.RemoveHTTPHandler("", m_URL);
392 m_capsDict.Remove(agentID);
393
394 if (m_pollservices.TryGetValue(agentID, out args))
395 {
396 m_pollservices.Remove(agentID);
397 }
398 }
399
400 private void DoTextureRequests()
401 {
402 while (true)
403 {
404 aPollRequest poolreq = m_queue.Dequeue();
405
406 poolreq.thepoll.Process(poolreq);
407 }
408 }
409 internal sealed class CapsDataThrottler
410 {
411
412 private volatile int currenttime = 0;
413 private volatile int lastTimeElapsed = 0;
414 private volatile int BytesSent = 0;
415 private int oversizedImages = 0;
416 public CapsDataThrottler(int pBytes, int max, int min)
417 {
418 ThrottleBytes = pBytes;
419 lastTimeElapsed = Util.EnvironmentTickCount();
420 }
421 public bool hasEvents(UUID key, Dictionary<UUID, GetTextureModule.aPollResponse> responses)
422 {
423 PassTime();
424 // Note, this is called IN LOCK
425 bool haskey = responses.ContainsKey(key);
426 if (!haskey)
427 {
428 return false;
429 }
430 GetTextureModule.aPollResponse response;
431 if (responses.TryGetValue(key, out response))
432 {
433 // This is any error response
434 if (response.bytes == 0)
435 return true;
436
437 // Normal
438 if (BytesSent + response.bytes <= ThrottleBytes)
439 {
440 BytesSent += response.bytes;
441 //TimeBasedAction timeBasedAction = new TimeBasedAction { byteRemoval = response.bytes, requestId = key, timeMS = currenttime + 1000, unlockyn = false };
442 //m_actions.Add(timeBasedAction);
443 return true;
444 }
445 // Big textures
446 else if (response.bytes > ThrottleBytes && oversizedImages <= ((ThrottleBytes % 50000) + 1))
447 {
448 Interlocked.Increment(ref oversizedImages);
449 BytesSent += response.bytes;
450 //TimeBasedAction timeBasedAction = new TimeBasedAction { byteRemoval = response.bytes, requestId = key, timeMS = currenttime + (((response.bytes % ThrottleBytes)+1)*1000) , unlockyn = false };
451 //m_actions.Add(timeBasedAction);
452 return true;
453 }
454 else
455 {
456 return false;
457 }
458 }
459
460 return haskey;
461 }
462
463 public void ProcessTime()
464 {
465 PassTime();
466 }
467
468 private void PassTime()
469 {
470 currenttime = Util.EnvironmentTickCount();
471 int timeElapsed = Util.EnvironmentTickCountSubtract(currenttime, lastTimeElapsed);
472 //processTimeBasedActions(responses);
473 if (Util.EnvironmentTickCountSubtract(currenttime, timeElapsed) >= 1000)
474 {
475 lastTimeElapsed = Util.EnvironmentTickCount();
476 BytesSent -= ThrottleBytes;
477 if (BytesSent < 0) BytesSent = 0;
478 if (BytesSent < ThrottleBytes)
479 {
480 oversizedImages = 0;
481 }
482 }
483 }
484 public int ThrottleBytes;
485 }
148 } 486 }
487
488
149} 489}