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