From f3e177814a30ee91a2fdd27f2a1aebf06a39cd15 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Mon, 17 Mar 2014 20:51:35 +0000 Subject: [PATCH] Add regression test for http inventory fetch. Involved some restructuring to allow regression tests to dequeue inventory requests and perform poll responses synchronously rather than async --- OpenSim/Capabilities/Caps.cs | 7 +- .../Servers/HttpServer/BaseHttpServer.cs | 24 +- .../HttpServer/PollServiceRequestManager.cs | 177 ++++++---- .../Framework/Servers/Tests/OSHttpTests.cs | 320 +----------------- .../Caps/Tests/WebFetchInvDescModuleTests.cs | 158 +++++++++ .../Linden/Caps/WebFetchInvDescModule.cs | 43 ++- .../Framework/Caps/CapabilitiesModule.cs | 4 + .../Common/Mock/TestHttpClientContext.cs | 110 ++++++ OpenSim/Tests/Common/Mock/TestHttpRequest.cs | 174 ++++++++++ OpenSim/Tests/Common/Mock/TestHttpResponse.cs | 171 ++++++++++ prebuild.xml | 3 + 11 files changed, 784 insertions(+), 407 deletions(-) create mode 100644 OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs create mode 100644 OpenSim/Tests/Common/Mock/TestHttpClientContext.cs create mode 100644 OpenSim/Tests/Common/Mock/TestHttpRequest.cs create mode 100644 OpenSim/Tests/Common/Mock/TestHttpResponse.cs diff --git a/OpenSim/Capabilities/Caps.cs b/OpenSim/Capabilities/Caps.cs index bbf3b27c51..049afab467 100644 --- a/OpenSim/Capabilities/Caps.cs +++ b/OpenSim/Capabilities/Caps.cs @@ -50,8 +50,7 @@ namespace OpenSim.Framework.Capabilities public class Caps { -// private static readonly ILog m_log = -// LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private string m_httpListenerHostName; private uint m_httpListenPort; @@ -152,6 +151,10 @@ namespace OpenSim.Framework.Capabilities public void RegisterPollHandler(string capName, PollServiceEventArgs pollServiceHandler) { +// m_log.DebugFormat( +// "[CAPS]: Registering handler with name {0}, url {1} for {2}", +// capName, pollServiceHandler.Url, m_agentID, m_regionName); + m_pollServiceHandlers.Add(capName, pollServiceHandler); m_httpListener.AddPollServiceHTTPHandler(pollServiceHandler.Url, pollServiceHandler); diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index e1ae74ee73..e243002d88 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -113,7 +113,7 @@ namespace OpenSim.Framework.Servers.HttpServer protected IPAddress m_listenIPAddress = IPAddress.Any; - private PollServiceRequestManager m_PollServiceManager; + public PollServiceRequestManager PollServiceRequestManager { get; private set; } public uint SSLPort { @@ -374,7 +374,7 @@ namespace OpenSim.Framework.Servers.HttpServer return true; } - private void OnRequest(object source, RequestEventArgs args) + public void OnRequest(object source, RequestEventArgs args) { RequestNumber++; @@ -429,7 +429,7 @@ namespace OpenSim.Framework.Servers.HttpServer psEvArgs.Request(psreq.RequestID, keysvals); } - m_PollServiceManager.Enqueue(psreq); + PollServiceRequestManager.Enqueue(psreq); } else { @@ -1781,10 +1781,17 @@ namespace OpenSim.Framework.Servers.HttpServer public void Start() { - StartHTTP(); + Start(true); } - private void StartHTTP() + /// + /// Start the http server + /// + /// + /// If true then poll responses are performed asynchronsly. + /// Option exists to allow regression tests to perform processing synchronously. + /// + public void Start(bool performPollResponsesAsync) { m_log.InfoFormat( "[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port); @@ -1822,8 +1829,9 @@ namespace OpenSim.Framework.Servers.HttpServer m_httpListener2.Start(64); // Long Poll Service Manager with 3 worker threads a 25 second timeout for no events - m_PollServiceManager = new PollServiceRequestManager(this, 3, 25000); - m_PollServiceManager.Start(); + PollServiceRequestManager = new PollServiceRequestManager(this, performPollResponsesAsync, 3, 25000); + PollServiceRequestManager.Start(); + HTTPDRunning = true; //HttpListenerContext context; @@ -1892,7 +1900,7 @@ namespace OpenSim.Framework.Servers.HttpServer try { - m_PollServiceManager.Stop(); + PollServiceRequestManager.Stop(); m_httpListener2.ExceptionThrown -= httpServerException; //m_httpListener2.DisconnectHandler = null; diff --git a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs index 6aa590708d..456acb0026 100644 --- a/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs +++ b/OpenSim/Framework/Servers/HttpServer/PollServiceRequestManager.cs @@ -44,6 +44,20 @@ namespace OpenSim.Framework.Servers.HttpServer { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + /// + /// Is the poll service request manager running? + /// + /// + /// Can be running either synchronously or asynchronously + /// + public bool IsRunning { get; private set; } + + /// + /// Is the poll service performing responses asynchronously (with its own threads) or synchronously (via + /// external calls)? + /// + public bool PerformResponsesAsync { get; private set; } + private readonly BaseHttpServer m_server; private BlockingQueue m_requests = new BlockingQueue(); @@ -52,48 +66,53 @@ namespace OpenSim.Framework.Servers.HttpServer private uint m_WorkerThreadCount = 0; private Thread[] m_workerThreads; - private bool m_running = true; - private SmartThreadPool m_threadPool = new SmartThreadPool(20000, 12, 2); // private int m_timeout = 1000; // increase timeout 250; now use the event one - public PollServiceRequestManager(BaseHttpServer pSrv, uint pWorkerThreadCount, int pTimeout) + public PollServiceRequestManager( + BaseHttpServer pSrv, bool performResponsesAsync, uint pWorkerThreadCount, int pTimeout) { m_server = pSrv; + PerformResponsesAsync = performResponsesAsync; m_WorkerThreadCount = pWorkerThreadCount; m_workerThreads = new Thread[m_WorkerThreadCount]; } public void Start() { - //startup worker threads - for (uint i = 0; i < m_WorkerThreadCount; i++) - { - m_workerThreads[i] - = Watchdog.StartThread( - PoolWorkerJob, - string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port), - ThreadPriority.Normal, - false, - false, - null, - int.MaxValue); - } + IsRunning = true; - Watchdog.StartThread( - this.CheckLongPollThreads, - string.Format("LongPollServiceWatcherThread:{0}", m_server.Port), - ThreadPriority.Normal, - false, - true, - null, - 1000 * 60 * 10); + if (PerformResponsesAsync) + { + //startup worker threads + for (uint i = 0; i < m_WorkerThreadCount; i++) + { + m_workerThreads[i] + = Watchdog.StartThread( + PoolWorkerJob, + string.Format("PollServiceWorkerThread{0}:{1}", i, m_server.Port), + ThreadPriority.Normal, + false, + false, + null, + int.MaxValue); + } + + Watchdog.StartThread( + this.CheckLongPollThreads, + string.Format("LongPollServiceWatcherThread:{0}", m_server.Port), + ThreadPriority.Normal, + false, + true, + null, + 1000 * 60 * 10); + } } private void ReQueueEvent(PollServiceHttpRequest req) { - if (m_running) + if (IsRunning) { // delay the enqueueing for 100ms. There's no need to have the event // actively on the queue @@ -109,7 +128,7 @@ namespace OpenSim.Framework.Servers.HttpServer public void Enqueue(PollServiceHttpRequest req) { - if (m_running) + if (IsRunning) { if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) { @@ -129,7 +148,7 @@ namespace OpenSim.Framework.Servers.HttpServer // All other types of tasks (Inventory handlers, http-in, etc) don't have the long-poll nature, // so if they aren't ready to be served by a worker thread (no events), they are placed // directly back in the "ready-to-serve" queue by the worker thread. - while (m_running) + while (IsRunning) { Thread.Sleep(500); Watchdog.UpdateThread(); @@ -137,7 +156,7 @@ namespace OpenSim.Framework.Servers.HttpServer // List not_ready = new List(); lock (m_longPollRequests) { - if (m_longPollRequests.Count > 0 && m_running) + if (m_longPollRequests.Count > 0 && IsRunning) { List ready = m_longPollRequests.FindAll(req => (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id) || // there are events in this EQ @@ -158,7 +177,7 @@ namespace OpenSim.Framework.Servers.HttpServer public void Stop() { - m_running = false; + IsRunning = false; // m_timeout = -10000; // cause all to expire Thread.Sleep(1000); // let the world move @@ -169,7 +188,7 @@ namespace OpenSim.Framework.Servers.HttpServer lock (m_longPollRequests) { - if (m_longPollRequests.Count > 0 && m_running) + if (m_longPollRequests.Count > 0 && IsRunning) m_longPollRequests.ForEach(req => m_requests.Enqueue(req)); } @@ -194,68 +213,82 @@ namespace OpenSim.Framework.Servers.HttpServer private void PoolWorkerJob() { - while (m_running) + while (IsRunning) { - PollServiceHttpRequest req = m_requests.Dequeue(5000); - //m_log.WarnFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString())); - Watchdog.UpdateThread(); - if (req != null) + WaitPerformResponse(); + } + } + + public void WaitPerformResponse() + { + PollServiceHttpRequest req = m_requests.Dequeue(5000); +// m_log.DebugFormat("[YYY]: Dequeued {0}", (req == null ? "null" : req.PollServiceArgs.Type.ToString())); + + if (req != null) + { + try { - try + if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id)) { - if (req.PollServiceArgs.HasEvents(req.RequestID, req.PollServiceArgs.Id)) + Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id); + + if (responsedata == null) + return; + + // This is the event queue. + // Even if we're not running we can still perform responses by explicit request. + if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll + || !PerformResponsesAsync) { - Hashtable responsedata = req.PollServiceArgs.GetEvents(req.RequestID, req.PollServiceArgs.Id); - - if (responsedata == null) - continue; - - if (req.PollServiceArgs.Type == PollServiceEventArgs.EventType.LongPoll) // This is the event queue + try + { + req.DoHTTPGruntWork(m_server, responsedata); + } + catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream + { + // Ignore it, no need to reply + m_log.Error(e); + } + } + else + { + m_threadPool.QueueWorkItem(x => { try { req.DoHTTPGruntWork(m_server, responsedata); } - catch (ObjectDisposedException) // Browser aborted before we could read body, server closed the stream + catch (ObjectDisposedException e) // Browser aborted before we could read body, server closed the stream { // Ignore it, no need to reply + m_log.Error(e); } - } - else - { - m_threadPool.QueueWorkItem(x => + catch (Exception e) { - try - { - req.DoHTTPGruntWork(m_server, responsedata); - } - catch (ObjectDisposedException) // Browser aborted before we could read body, server closed the stream - { - // Ignore it, no need to reply - } + m_log.Error(e); + } - return null; - }, null); - } + return null; + }, null); + } + } + else + { + if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) + { + req.DoHTTPGruntWork( + m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id)); } else { - if ((Environment.TickCount - req.RequestTime) > req.PollServiceArgs.TimeOutms) - { - req.DoHTTPGruntWork( - m_server, req.PollServiceArgs.NoEvents(req.RequestID, req.PollServiceArgs.Id)); - } - else - { - ReQueueEvent(req); - } + ReQueueEvent(req); } } - catch (Exception e) - { - m_log.ErrorFormat("Exception in poll service thread: " + e.ToString()); - } + } + catch (Exception e) + { + m_log.ErrorFormat("Exception in poll service thread: " + e.ToString()); } } } diff --git a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs index 5b912b4ea2..5c0e0df1b2 100644 --- a/OpenSim/Framework/Servers/Tests/OSHttpTests.cs +++ b/OpenSim/Framework/Servers/Tests/OSHttpTests.cs @@ -41,323 +41,7 @@ namespace OpenSim.Framework.Servers.Tests { [TestFixture] public class OSHttpTests : OpenSimTestCase - { - // we need an IHttpClientContext for our tests - public class TestHttpClientContext: IHttpClientContext - { - private bool _secured; - public bool IsSecured - { - get { return _secured; } - } - public bool Secured - { - get { return _secured; } - } - - public TestHttpClientContext(bool secured) - { - _secured = secured; - } - - public void Disconnect(SocketError error) {} - public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {} - public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {} - public void Respond(string body) {} - public void Send(byte[] buffer) {} - public void Send(byte[] buffer, int offset, int size) {} - public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {} - public void Close() { } - public bool EndWhenDone { get { return false;} set { return;}} - - public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() - { - return new HTTPNetworkContext(); - } - - public event EventHandler Disconnected = delegate { }; - /// - /// A request have been received in the context. - /// - public event EventHandler RequestReceived = delegate { }; - - } - - public class TestHttpRequest: IHttpRequest - { - private string _uriPath; - public bool BodyIsComplete - { - get { return true; } - } - public string[] AcceptTypes - { - get {return _acceptTypes; } - } - private string[] _acceptTypes; - public Stream Body - { - get { return _body; } - set { _body = value;} - } - private Stream _body; - public ConnectionType Connection - { - get { return _connection; } - set { _connection = value; } - } - private ConnectionType _connection; - public int ContentLength - { - get { return _contentLength; } - set { _contentLength = value; } - } - private int _contentLength; - public NameValueCollection Headers - { - get { return _headers; } - } - private NameValueCollection _headers = new NameValueCollection(); - public string HttpVersion - { - get { return _httpVersion; } - set { _httpVersion = value; } - } - private string _httpVersion = null; - public string Method - { - get { return _method; } - set { _method = value; } - } - private string _method = null; - public HttpInput QueryString - { - get { return _queryString; } - } - private HttpInput _queryString = null; - public Uri Uri - { - get { return _uri; } - set { _uri = value; } - } - private Uri _uri = null; - public string[] UriParts - { - get { return _uri.Segments; } - } - public HttpParam Param - { - get { return null; } - } - public HttpForm Form - { - get { return null; } - } - public bool IsAjax - { - get { return false; } - } - public RequestCookies Cookies - { - get { return null; } - } - - public TestHttpRequest() {} - - public TestHttpRequest(string contentEncoding, string contentType, string userAgent, - string remoteAddr, string remotePort, string[] acceptTypes, - ConnectionType connectionType, int contentLength, Uri uri) - { - _headers["content-encoding"] = contentEncoding; - _headers["content-type"] = contentType; - _headers["user-agent"] = userAgent; - _headers["remote_addr"] = remoteAddr; - _headers["remote_port"] = remotePort; - - _acceptTypes = acceptTypes; - _connection = connectionType; - _contentLength = contentLength; - _uri = uri; - } - - public void DecodeBody(FormDecoderProvider providers) {} - public void SetCookies(RequestCookies cookies) {} - public void AddHeader(string name, string value) - { - _headers.Add(name, value); - } - public int AddToBody(byte[] bytes, int offset, int length) - { - return 0; - } - public void Clear() {} - - public object Clone() - { - TestHttpRequest clone = new TestHttpRequest(); - clone._acceptTypes = _acceptTypes; - clone._connection = _connection; - clone._contentLength = _contentLength; - clone._uri = _uri; - clone._headers = new NameValueCollection(_headers); - - return clone; - } - public IHttpResponse CreateResponse(IHttpClientContext context) - { - return new HttpResponse(context, this); - } - /// - /// Path and query (will be merged with the host header) and put in Uri - /// - /// - public string UriPath - { - get { return _uriPath; } - set - { - _uriPath = value; - - } - } - - } - - public class TestHttpResponse: IHttpResponse - { - public Stream Body - { - get { return _body; } - - set { _body = value; } - } - private Stream _body; - - public string ProtocolVersion - { - get { return _protocolVersion; } - set { _protocolVersion = value; } - } - private string _protocolVersion; - - public bool Chunked - { - get { return _chunked; } - - set { _chunked = value; } - } - private bool _chunked; - - public ConnectionType Connection - { - get { return _connection; } - - set { _connection = value; } - } - private ConnectionType _connection; - - public Encoding Encoding - { - get { return _encoding; } - - set { _encoding = value; } - } - private Encoding _encoding; - - public int KeepAlive - { - get { return _keepAlive; } - - set { _keepAlive = value; } - } - private int _keepAlive; - - public HttpStatusCode Status - { - get { return _status; } - - set { _status = value; } - } - private HttpStatusCode _status; - - public string Reason - { - get { return _reason; } - - set { _reason = value; } - } - private string _reason; - - public long ContentLength - { - get { return _contentLength; } - - set { _contentLength = value; } - } - private long _contentLength; - - public string ContentType - { - get { return _contentType; } - - set { _contentType = value; } - } - private string _contentType; - - public bool HeadersSent - { - get { return _headersSent; } - } - private bool _headersSent; - - public bool Sent - { - get { return _sent; } - } - private bool _sent; - - public ResponseCookies Cookies - { - get { return _cookies; } - } - private ResponseCookies _cookies = null; - - public TestHttpResponse() - { - _headersSent = false; - _sent = false; - } - - public void AddHeader(string name, string value) {} - public void Send() - { - if (!_headersSent) SendHeaders(); - if (_sent) throw new InvalidOperationException("stuff already sent"); - _sent = true; - } - - public void SendBody(byte[] buffer, int offset, int count) - { - if (!_headersSent) SendHeaders(); - _sent = true; - } - public void SendBody(byte[] buffer) - { - if (!_headersSent) SendHeaders(); - _sent = true; - } - - public void SendHeaders() - { - if (_headersSent) throw new InvalidOperationException("headers already sent"); - _headersSent = true; - } - - public void Redirect(Uri uri) {} - public void Redirect(string url) {} - } - - + { public OSHttpRequest req0; public OSHttpRequest req1; @@ -429,4 +113,4 @@ namespace OpenSim.Framework.Servers.Tests Assert.That(rsp0.ContentType, Is.EqualTo("text/xml")); } } -} +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs b/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs new file mode 100644 index 0000000000..edc5016064 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/Caps/Tests/WebFetchInvDescModuleTests.cs @@ -0,0 +1,158 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using HttpServer; +using log4net.Config; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using OpenSim.Tests.Common.Mock; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; + +namespace OpenSim.Region.ClientStack.Linden.Caps.Tests +{ + [TestFixture] + public class WebFetchInvDescModuleTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void TestFixtureSetUp() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TestFixureTearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + uint port = 9999; + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, 0, ""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + + server.Start(false); + } + + [Test] + public void TestInventoryDescendentsFetch() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + BaseHttpServer httpServer = MainServer.Instance; + Scene scene = new SceneHelpers().SetupScene(); + + CapabilitiesModule capsModule = new CapabilitiesModule(); + WebFetchInvDescModule wfidModule = new WebFetchInvDescModule(false); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("ClientStack.LindenCaps"); + config.Configs["ClientStack.LindenCaps"].Set("Cap_FetchInventoryDescendents2", "localhost"); + + SceneHelpers.SetupSceneModules(scene, config, capsModule, wfidModule); + + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + + // We need a user present to have any capabilities set up + SceneHelpers.AddScenePresence(scene, ua.PrincipalID); + + TestHttpRequest req = new TestHttpRequest(); + OpenSim.Framework.Capabilities.Caps userCaps = capsModule.GetCapsForUser(ua.PrincipalID); + PollServiceEventArgs pseArgs; + userCaps.TryGetPollHandler("FetchInventoryDescendents2", out pseArgs); + req.UriPath = pseArgs.Url; + req.Uri = new Uri(req.UriPath); + + // Retrieve root folder details directly so that we can request + InventoryFolderBase folder = scene.InventoryService.GetRootFolder(ua.PrincipalID); + + OSDMap osdFolder = new OSDMap(); + osdFolder["folder_id"] = folder.ID; + osdFolder["owner_id"] = ua.PrincipalID; + osdFolder["fetch_folders"] = true; + osdFolder["fetch_items"] = true; + osdFolder["sort_order"] = 0; + + OSDArray osdFoldersArray = new OSDArray(); + osdFoldersArray.Add(osdFolder); + + OSDMap osdReqMap = new OSDMap(); + osdReqMap["folders"] = osdFoldersArray; + + req.Body = new MemoryStream(OSDParser.SerializeLLSDXmlBytes(osdReqMap)); + + TestHttpClientContext context = new TestHttpClientContext(false); + MainServer.Instance.OnRequest(context, new RequestEventArgs(req)); + + // Drive processing of the queued inventory request synchronously. + wfidModule.WaitProcessQueuedInventoryRequest(); + MainServer.Instance.PollServiceRequestManager.WaitPerformResponse(); + +// System.Threading.Thread.Sleep(10000); + + OSDMap responseOsd = (OSDMap)OSDParser.DeserializeLLSDXml(context.ResponseBody); + OSDArray foldersOsd = (OSDArray)responseOsd["folders"]; + OSDMap folderOsd = (OSDMap)foldersOsd[0]; + + // A sanity check that the response has the expected number of descendents for a default inventory + // TODO: Need a more thorough check. + Assert.That((int)folderOsd["descendents"], Is.EqualTo(14)); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs index 340d2e7908..f0dccda168 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/WebFetchInvDescModule.cs @@ -65,6 +65,15 @@ namespace OpenSim.Region.ClientStack.Linden // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Control whether requests will be processed asynchronously. + /// + /// + /// Defaults to true. Can currently not be changed once a region has been added to the module. + /// + public bool ProcessQueuedRequestsAsync { get; private set; } + private Scene m_scene; private IInventoryService m_InventoryService; @@ -84,6 +93,13 @@ namespace OpenSim.Region.ClientStack.Linden #region ISharedRegionModule Members + public WebFetchInvDescModule() : this(true) {} + + public WebFetchInvDescModule(bool processQueuedResultsAsync) + { + ProcessQueuedRequestsAsync = processQueuedResultsAsync; + } + public void Initialise(IConfigSource source) { IConfig config = source.Configs["ClientStack.LindenCaps"]; @@ -114,8 +130,16 @@ namespace OpenSim.Region.ClientStack.Linden m_scene.EventManager.OnRegisterCaps -= RegisterCaps; - foreach (Thread t in m_workerThreads) - Watchdog.AbortThread(t.ManagedThreadId); + if (ProcessQueuedRequestsAsync) + { + if (m_workerThreads != null) + { + foreach (Thread t in m_workerThreads) + Watchdog.AbortThread(t.ManagedThreadId); + + m_workerThreads = null; + } + } m_scene = null; } @@ -133,7 +157,7 @@ namespace OpenSim.Region.ClientStack.Linden m_scene.EventManager.OnRegisterCaps += RegisterCaps; - if (m_workerThreads == null) + if (ProcessQueuedRequestsAsync && m_workerThreads == null) { m_workerThreads = new Thread[2]; @@ -358,11 +382,16 @@ namespace OpenSim.Region.ClientStack.Linden { Watchdog.UpdateThread(); - aPollRequest poolreq = m_queue.Dequeue(); - - if (poolreq != null && poolreq.thepoll != null) - poolreq.thepoll.Process(poolreq); + WaitProcessQueuedInventoryRequest(); } } + + public void WaitProcessQueuedInventoryRequest() + { + aPollRequest poolreq = m_queue.Dequeue(); + + if (poolreq != null && poolreq.thepoll != null) + poolreq.thepoll.Process(poolreq); + } } } diff --git a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs index 13cc99a6d1..817ef85d45 100644 --- a/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Caps/CapabilitiesModule.cs @@ -137,6 +137,10 @@ namespace OpenSim.Region.CoreModules.Framework // agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath); } +// m_log.DebugFormat( +// "[CAPS]: Adding capabilities for agent {0} in {1} with path {2}", +// agentId, m_scene.RegionInfo.RegionName, capsObjectPath); + caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName, (MainServer.Instance == null) ? 0: MainServer.Instance.Port, capsObjectPath, agentId, m_scene.RegionInfo.RegionName); diff --git a/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs b/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs new file mode 100644 index 0000000000..5a55b09590 --- /dev/null +++ b/OpenSim/Tests/Common/Mock/TestHttpClientContext.cs @@ -0,0 +1,110 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using HttpServer; +using OpenSim.Framework; + +namespace OpenSim.Tests.Common +{ + public class TestHttpClientContext: IHttpClientContext + { + /// + /// Bodies of responses from the server. + /// + public string ResponseBody + { + get { return Encoding.UTF8.GetString(m_responseStream.ToArray()); } + } + + public Byte[] ResponseBodyBytes + { + get{ return m_responseStream.ToArray(); } + } + + private MemoryStream m_responseStream = new MemoryStream(); + + public bool IsSecured { get; set; } + + public bool Secured + { + get { return IsSecured; } + set { IsSecured = value; } + } + + public TestHttpClientContext(bool secured) + { + Secured = secured; + } + + public void Disconnect(SocketError error) + { +// Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); + } + + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} + public void Respond(string body) { Console.WriteLine("xxx");} + + public void Send(byte[] buffer) + { + // Getting header data here +// Console.WriteLine("xxxx: Got {0}", Encoding.UTF8.GetString(buffer)); + } + + public void Send(byte[] buffer, int offset, int size) + { +// Util.PrintCallStack(); +// +// Console.WriteLine( +// "TestHttpClientContext.Send(byte[], int, int) got offset={0}, size={1}, buffer={2}", +// offset, size, Encoding.UTF8.GetString(buffer)); + + m_responseStream.Write(buffer, offset, size); + } + + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {Console.WriteLine("xxxxxx");} + public void Close() { } + public bool EndWhenDone { get { return false;} set { return;}} + + public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() + { + return new HTTPNetworkContext(); + } + + public event EventHandler Disconnected = delegate { }; + /// + /// A request have been received in the context. + /// + public event EventHandler RequestReceived = delegate { }; + } +} \ No newline at end of file diff --git a/OpenSim/Tests/Common/Mock/TestHttpRequest.cs b/OpenSim/Tests/Common/Mock/TestHttpRequest.cs new file mode 100644 index 0000000000..b86889575f --- /dev/null +++ b/OpenSim/Tests/Common/Mock/TestHttpRequest.cs @@ -0,0 +1,174 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Specialized; +using System.IO; +using HttpServer; +using HttpServer.FormDecoders; + +namespace OpenSim.Tests.Common +{ + public class TestHttpRequest: IHttpRequest + { + private string _uriPath; + public bool BodyIsComplete + { + get { return true; } + } + public string[] AcceptTypes + { + get {return _acceptTypes; } + } + private string[] _acceptTypes; + public Stream Body + { + get { return _body; } + set { _body = value;} + } + private Stream _body; + public ConnectionType Connection + { + get { return _connection; } + set { _connection = value; } + } + private ConnectionType _connection; + public int ContentLength + { + get { return _contentLength; } + set { _contentLength = value; } + } + private int _contentLength; + public NameValueCollection Headers + { + get { return _headers; } + } + private NameValueCollection _headers = new NameValueCollection(); + + public string HttpVersion { get; set; } + + public string Method + { + get { return _method; } + set { _method = value; } + } + private string _method = null; + public HttpInput QueryString + { + get { return _queryString; } + } + private HttpInput _queryString = null; + public Uri Uri + { + get { return _uri; } + set { _uri = value; } + } + private Uri _uri = null; + public string[] UriParts + { + get { return _uri.Segments; } + } + public HttpParam Param + { + get { return null; } + } + public HttpForm Form + { + get { return null; } + } + public bool IsAjax + { + get { return false; } + } + public RequestCookies Cookies + { + get { return null; } + } + + public TestHttpRequest() + { + HttpVersion = "HTTP/1.1"; + } + + public TestHttpRequest(string contentEncoding, string contentType, string userAgent, + string remoteAddr, string remotePort, string[] acceptTypes, + ConnectionType connectionType, int contentLength, Uri uri) : base() + { + _headers["content-encoding"] = contentEncoding; + _headers["content-type"] = contentType; + _headers["user-agent"] = userAgent; + _headers["remote_addr"] = remoteAddr; + _headers["remote_port"] = remotePort; + + _acceptTypes = acceptTypes; + _connection = connectionType; + _contentLength = contentLength; + _uri = uri; + } + + public void DecodeBody(FormDecoderProvider providers) {} + public void SetCookies(RequestCookies cookies) {} + public void AddHeader(string name, string value) + { + _headers.Add(name, value); + } + public int AddToBody(byte[] bytes, int offset, int length) + { + return 0; + } + public void Clear() {} + + public object Clone() + { + TestHttpRequest clone = new TestHttpRequest(); + clone._acceptTypes = _acceptTypes; + clone._connection = _connection; + clone._contentLength = _contentLength; + clone._uri = _uri; + clone._headers = new NameValueCollection(_headers); + + return clone; + } + public IHttpResponse CreateResponse(IHttpClientContext context) + { + return new HttpResponse(context, this); + } + /// + /// Path and query (will be merged with the host header) and put in Uri + /// + /// + public string UriPath + { + get { return _uriPath; } + set + { + _uriPath = value; + + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Tests/Common/Mock/TestHttpResponse.cs b/OpenSim/Tests/Common/Mock/TestHttpResponse.cs new file mode 100644 index 0000000000..ff47c1014d --- /dev/null +++ b/OpenSim/Tests/Common/Mock/TestHttpResponse.cs @@ -0,0 +1,171 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Net; +using System.Text; +using HttpServer; + +namespace OpenSim.Tests.Common +{ + public class TestHttpResponse: IHttpResponse + { + public Stream Body + { + get { return _body; } + + set { _body = value; } + } + private Stream _body; + + public string ProtocolVersion + { + get { return _protocolVersion; } + set { _protocolVersion = value; } + } + private string _protocolVersion; + + public bool Chunked + { + get { return _chunked; } + + set { _chunked = value; } + } + private bool _chunked; + + public ConnectionType Connection + { + get { return _connection; } + + set { _connection = value; } + } + private ConnectionType _connection; + + public Encoding Encoding + { + get { return _encoding; } + + set { _encoding = value; } + } + private Encoding _encoding; + + public int KeepAlive + { + get { return _keepAlive; } + + set { _keepAlive = value; } + } + private int _keepAlive; + + public HttpStatusCode Status + { + get { return _status; } + + set { _status = value; } + } + private HttpStatusCode _status; + + public string Reason + { + get { return _reason; } + + set { _reason = value; } + } + private string _reason; + + public long ContentLength + { + get { return _contentLength; } + + set { _contentLength = value; } + } + private long _contentLength; + + public string ContentType + { + get { return _contentType; } + + set { _contentType = value; } + } + private string _contentType; + + public bool HeadersSent + { + get { return _headersSent; } + } + private bool _headersSent; + + public bool Sent + { + get { return _sent; } + } + private bool _sent; + + public ResponseCookies Cookies + { + get { return _cookies; } + } + private ResponseCookies _cookies = null; + + public TestHttpResponse() + { + _headersSent = false; + _sent = false; + } + + public void AddHeader(string name, string value) {} + + public void Send() + { + if (!_headersSent) SendHeaders(); + if (_sent) throw new InvalidOperationException("stuff already sent"); + _sent = true; + } + + public void SendBody(byte[] buffer, int offset, int count) + { + if (!_headersSent) SendHeaders(); + _sent = true; + } + + public void SendBody(byte[] buffer) + { + if (!_headersSent) SendHeaders(); + _sent = true; + } + + public void SendHeaders() + { + if (_headersSent) throw new InvalidOperationException("headers already sent"); + _headersSent = true; + } + + public void Redirect(Uri uri) {} + public void Redirect(string url) {} + } +} \ No newline at end of file diff --git a/prebuild.xml b/prebuild.xml index 356110a0e1..c16d6e6e81 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -3321,12 +3321,14 @@ ../../../../../bin/ + + @@ -3341,6 +3343,7 @@ +