OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
GetTextureModule.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Reflection;
32 using System.Threading;
33 using log4net;
34 using Nini.Config;
35 using Mono.Addins;
36 using OpenMetaverse;
37 using OpenSim.Framework;
38 using OpenSim.Framework.Servers;
39 using OpenSim.Framework.Servers.HttpServer;
40 using OpenSim.Region.Framework.Interfaces;
41 using OpenSim.Region.Framework.Scenes;
42 using OpenSim.Services.Interfaces;
44 using OpenSim.Capabilities.Handlers;
45 using OpenSim.Framework.Monitoring;
46 
47 namespace OpenSim.Region.ClientStack.Linden
48 {
49 
50  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "GetTextureModule")]
52  {
53 
54  struct aPollRequest
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 
71  private Scene m_scene;
72 
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  private static int m_NumberScenes = 0;
80  private static OpenMetaverse.BlockingQueue<aPollRequest> m_queue =
81  new OpenMetaverse.BlockingQueue<aPollRequest>();
82 
83  private Dictionary<UUID,PollServiceTextureEventArgs> m_pollservices = new Dictionary<UUID,PollServiceTextureEventArgs>();
84 
85  private string m_Url = "localhost";
86 
87  #region ISharedRegionModule Members
88 
89  public void Initialise(IConfigSource source)
90  {
91  IConfig config = source.Configs["ClientStack.LindenCaps"];
92 
93  if (config == null)
94  return;
95 /*
96  m_URL = config.GetString("Cap_GetTexture", string.Empty);
97  // Cap doesn't exist
98  if (m_URL != string.Empty)
99  {
100  m_Enabled = true;
101  m_RedirectURL = config.GetString("GetTextureRedirectURL");
102  }
103 */
104  m_Url = config.GetString("Cap_GetTexture", "localhost");
105  }
106 
107  public void AddRegion(Scene s)
108  {
109  m_scene = s;
110  m_assetService = s.AssetService;
111  }
112 
113  public void RemoveRegion(Scene s)
114  {
115  m_scene.EventManager.OnRegisterCaps -= RegisterCaps;
116  m_scene.EventManager.OnDeregisterCaps -= DeregisterCaps;
117  m_scene.EventManager.OnThrottleUpdate -= ThrottleUpdate;
118  m_NumberScenes--;
119  m_scene = null;
120  }
121 
122  public void RegionLoaded(Scene s)
123  {
124  // We'll reuse the same handler for all requests.
125  m_getTextureHandler = new GetTextureHandler(m_assetService);
126 
127  m_scene.EventManager.OnRegisterCaps += RegisterCaps;
128  m_scene.EventManager.OnDeregisterCaps += DeregisterCaps;
129  m_scene.EventManager.OnThrottleUpdate += ThrottleUpdate;
130 
131  m_NumberScenes++;
132 
133  if (m_workerThreads == null)
134  {
135  m_workerThreads = new Thread[2];
136 
137  for (uint i = 0; i < 2; i++)
138  {
139  m_workerThreads[i] = WorkManager.StartThread(DoTextureRequests,
140  String.Format("GetTextureWorker{0}", i),
141  ThreadPriority.Normal,
142  false,
143  false,
144  null,
145  int.MaxValue);
146  }
147  }
148  }
149  private int ExtractImageThrottle(byte[] pthrottles)
150  {
151 
152  byte[] adjData;
153  int pos = 0;
154 
155  if (!BitConverter.IsLittleEndian)
156  {
157  byte[] newData = new byte[7 * 4];
158  Buffer.BlockCopy(pthrottles, 0, newData, 0, 7 * 4);
159 
160  for (int i = 0; i < 7; i++)
161  Array.Reverse(newData, i * 4, 4);
162 
163  adjData = newData;
164  }
165  else
166  {
167  adjData = pthrottles;
168  }
169 
170  pos = pos + 20;
171  int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); //pos += 4;
172  //int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f);
173  return texture;
174  }
175 
176  // 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.
178  {
179  byte[] throttles = p.ControllingClient.GetThrottlesPacked(1);
180  UUID user = p.UUID;
181  int imagethrottle = ExtractImageThrottle(throttles);
182  PollServiceTextureEventArgs args;
183  if (m_pollservices.TryGetValue(user,out args))
184  {
185  args.UpdateThrottle(imagethrottle);
186  }
187  }
188 
189  public void PostInitialise()
190  {
191  }
192 
193  public void Close()
194  {
195  if(m_NumberScenes <= 0 && m_workerThreads != null)
196  {
197  m_log.DebugFormat("[GetTextureModule] Closing");
198 
199  foreach (Thread t in m_workerThreads)
200  Watchdog.AbortThread(t.ManagedThreadId);
201 
202  m_queue.Clear();
203  }
204  }
205 
206  public string Name { get { return "GetTextureModule"; } }
207 
208  public Type ReplaceableInterface
209  {
210  get { return null; }
211  }
212 
213  #endregion
214 
215  private class PollServiceTextureEventArgs : PollServiceEventArgs
216  {
217  private List<Hashtable> requests =
218  new List<Hashtable>();
219  private Dictionary<UUID, aPollResponse> responses =
220  new Dictionary<UUID, aPollResponse>();
221 
222  private Scene m_scene;
223  private CapsDataThrottler m_throttler = new CapsDataThrottler(100000, 1400000,10000);
224  public PollServiceTextureEventArgs(UUID pId, Scene scene) :
225  base(null, "", null, null, null, pId, int.MaxValue)
226  {
227  m_scene = scene;
228  // x is request id, y is userid
229  HasEvents = (x, y) =>
230  {
231  lock (responses)
232  {
233  bool ret = m_throttler.hasEvents(x, responses);
234  m_throttler.ProcessTime();
235  return ret;
236 
237  }
238  };
239  GetEvents = (x, y) =>
240  {
241  lock (responses)
242  {
243  try
244  {
245  return responses[x].response;
246  }
247  finally
248  {
249  responses.Remove(x);
250  }
251  }
252  };
253  // x is request id, y is request data hashtable
254  Request = (x, y) =>
255  {
256  aPollRequest reqinfo = new aPollRequest();
257  reqinfo.thepoll = this;
258  reqinfo.reqID = x;
259  reqinfo.request = y;
260  reqinfo.send503 = false;
261 
262  lock (responses)
263  {
264  if (responses.Count > 0)
265  {
266  if (m_queue.Count >= 4)
267  {
268  // Never allow more than 4 fetches to wait
269  reqinfo.send503 = true;
270  }
271  }
272  }
273  m_queue.Enqueue(reqinfo);
274  };
275 
276  // this should never happen except possible on shutdown
277  NoEvents = (x, y) =>
278  {
279 /*
280  lock (requests)
281  {
282  Hashtable request = requests.Find(id => id["RequestID"].ToString() == x.ToString());
283  requests.Remove(request);
284  }
285 */
286  Hashtable response = new Hashtable();
287 
288  response["int_response_code"] = 500;
289  response["str_response_string"] = "Script timeout";
290  response["content_type"] = "text/plain";
291  response["keepalive"] = false;
292  response["reusecontext"] = false;
293 
294  return response;
295  };
296  }
297 
298  public void Process(aPollRequest requestinfo)
299  {
300  Hashtable response;
301 
302  UUID requestID = requestinfo.reqID;
303 
304  if(m_scene.ShuttingDown)
305  return;
306 
307  if (requestinfo.send503)
308  {
309  response = new Hashtable();
310 
311  response["int_response_code"] = 503;
312  response["str_response_string"] = "Throttled";
313  response["content_type"] = "text/plain";
314  response["keepalive"] = false;
315  response["reusecontext"] = false;
316 
317  Hashtable headers = new Hashtable();
318  headers["Retry-After"] = 30;
319  response["headers"] = headers;
320 
321  lock (responses)
322  responses[requestID] = new aPollResponse() {bytes = 0, response = response};
323 
324  return;
325  }
326 
327  // If the avatar is gone, don't bother to get the texture
328  if (m_scene.GetScenePresence(Id) == null)
329  {
330  response = new Hashtable();
331 
332  response["int_response_code"] = 500;
333  response["str_response_string"] = "Script timeout";
334  response["content_type"] = "text/plain";
335  response["keepalive"] = false;
336  response["reusecontext"] = false;
337 
338  lock (responses)
339  responses[requestID] = new aPollResponse() {bytes = 0, response = response};
340 
341  return;
342  }
343 
344  response = m_getTextureHandler.Handle(requestinfo.request);
345  lock (responses)
346  {
347  responses[requestID] = new aPollResponse()
348  {
349  bytes = (int) response["int_bytes"],
350  response = response
351  };
352 
353  }
354  m_throttler.ProcessTime();
355  }
356 
357  internal void UpdateThrottle(int pimagethrottle)
358  {
359  m_throttler.ThrottleBytes = pimagethrottle;
360  }
361  }
362 
363  private void RegisterCaps(UUID agentID, Caps caps)
364  {
365  if (m_Url == "localhost")
366  {
367  string capUrl = "/CAPS/" + UUID.Random() + "/";
368 
369  // Register this as a poll service
370  PollServiceTextureEventArgs args = new PollServiceTextureEventArgs(agentID, m_scene);
371 
372  args.Type = PollServiceEventArgs.EventType.Texture;
373  MainServer.Instance.AddPollServiceHTTPHandler(capUrl, args);
374 
375  string hostName = m_scene.RegionInfo.ExternalHostName;
376  uint port = (MainServer.Instance == null) ? 0 : MainServer.Instance.Port;
377  string protocol = "http";
378 
380  {
381  hostName = MainServer.Instance.SSLCommonName;
382  port = MainServer.Instance.SSLPort;
383  protocol = "https";
384  }
385  IExternalCapsModule handler = m_scene.RequestModuleInterface<IExternalCapsModule>();
386  if (handler != null)
387  handler.RegisterExternalUserCapsHandler(agentID, caps, "GetTexture", capUrl);
388  else
389  caps.RegisterHandler("GetTexture", String.Format("{0}://{1}:{2}{3}", protocol, hostName, port, capUrl));
390  m_pollservices[agentID] = args;
391  m_capsDict[agentID] = capUrl;
392  }
393  else
394  {
395  caps.RegisterHandler("GetTexture", m_Url);
396  }
397  }
398 
399  private void DeregisterCaps(UUID agentID, Caps caps)
400  {
401  PollServiceTextureEventArgs args;
402 
403  MainServer.Instance.RemoveHTTPHandler("", m_Url);
404  m_capsDict.Remove(agentID);
405 
406  if (m_pollservices.TryGetValue(agentID, out args))
407  {
408  m_pollservices.Remove(agentID);
409  }
410  }
411 
412  private static void DoTextureRequests()
413  {
414  while (true)
415  {
416  aPollRequest poolreq = m_queue.Dequeue();
417  Watchdog.UpdateThread();
418  poolreq.thepoll.Process(poolreq);
419  }
420  }
421 
422  internal sealed class CapsDataThrottler
423  {
424 
425  private volatile int currenttime = 0;
426  private volatile int lastTimeElapsed = 0;
427  private volatile int BytesSent = 0;
428  private int oversizedImages = 0;
429  public CapsDataThrottler(int pBytes, int max, int min)
430  {
431  ThrottleBytes = pBytes;
432  lastTimeElapsed = Util.EnvironmentTickCount();
433  }
434  public bool hasEvents(UUID key, Dictionary<UUID, GetTextureModule.aPollResponse> responses)
435  {
436  PassTime();
437  // Note, this is called IN LOCK
438  bool haskey = responses.ContainsKey(key);
439  if (!haskey)
440  {
441  return false;
442  }
443  GetTextureModule.aPollResponse response;
444  if (responses.TryGetValue(key, out response))
445  {
446  // This is any error response
447  if (response.bytes == 0)
448  return true;
449 
450  // Normal
451  if (BytesSent + response.bytes <= ThrottleBytes)
452  {
453  BytesSent += response.bytes;
454  //TimeBasedAction timeBasedAction = new TimeBasedAction { byteRemoval = response.bytes, requestId = key, timeMS = currenttime + 1000, unlockyn = false };
455  //m_actions.Add(timeBasedAction);
456  return true;
457  }
458  // Big textures
459  else if (response.bytes > ThrottleBytes && oversizedImages <= ((ThrottleBytes % 50000) + 1))
460  {
461  Interlocked.Increment(ref oversizedImages);
462  BytesSent += response.bytes;
463  //TimeBasedAction timeBasedAction = new TimeBasedAction { byteRemoval = response.bytes, requestId = key, timeMS = currenttime + (((response.bytes % ThrottleBytes)+1)*1000) , unlockyn = false };
464  //m_actions.Add(timeBasedAction);
465  return true;
466  }
467  else
468  {
469  return false;
470  }
471  }
472 
473  return haskey;
474  }
475 
476  public void ProcessTime()
477  {
478  PassTime();
479  }
480 
481  private void PassTime()
482  {
483  currenttime = Util.EnvironmentTickCount();
484  int timeElapsed = Util.EnvironmentTickCountSubtract(currenttime, lastTimeElapsed);
485  //processTimeBasedActions(responses);
486  if (Util.EnvironmentTickCountSubtract(currenttime, timeElapsed) >= 1000)
487  {
488  lastTimeElapsed = Util.EnvironmentTickCount();
489  BytesSent -= ThrottleBytes;
490  if (BytesSent < 0) BytesSent = 0;
491  if (BytesSent < ThrottleBytes)
492  {
493  oversizedImages = 0;
494  }
495  }
496  }
497  public int ThrottleBytes;
498  }
499  }
500 }
void AddRegion(Scene s)
This is called whenever a Scene is added. For shared modules, this can happen several times...
OpenSim.Framework.Capabilities.Caps Caps
void Initialise(IConfigSource source)
This is called to initialize the region module. For shared modules, this is called exactly once...
void RegionLoaded(Scene s)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
OpenSim.Framework.Capabilities.Caps Caps
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
Definition: ICM_Api.cs:31
static BaseHttpServer Instance
Set the main HTTP server instance.
Definition: MainServer.cs:83
void RemoveRegion(Scene s)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...