diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 659d42ff91..f6e29771d8 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -737,7 +737,7 @@ namespace OpenSim.Framework bool IsActive { get; set; } /// - /// Determines whether the client is logging out or not. + /// Determines whether the client is or has been removed from a given scene /// bool IsLoggingOut { get; set; } diff --git a/OpenSim/Region/Framework/Scenes/AsyncInventorySender.cs b/OpenSim/Region/Framework/Scenes/AsyncInventorySender.cs new file mode 100644 index 0000000000..06cd14b553 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/AsyncInventorySender.cs @@ -0,0 +1,156 @@ +/* + * 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.Reflection; +using System.Threading; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.Framework.Scenes +{ + class FetchHolder + { + public IClientAPI Client { get; private set; } + public UUID ItemID { get; private set; } + + public FetchHolder(IClientAPI client, UUID itemID) + { + Client = client; + ItemID = itemID; + } + } + + /// + /// Send FetchInventoryReply information to clients asynchronously on a single thread rather than asynchronously via + /// multiple threads. + /// + /// + /// If the main root inventory is right-clicked on a version 1 viewer for a user with a large inventory, a very + /// very large number of FetchInventory requests are sent to the simulator. Each is handled on a separate thread + /// by the IClientAPI, but the sheer number of requests overwhelms the number of threads available and ends up + /// freezing the inbound packet handling. + /// + /// This class makes the first FetchInventory packet thread process the queue. If new requests come + /// in while it is processing, then the subsequent threads just add the requests and leave it to the original + /// thread to process them. + /// + /// This might slow down outbound packets but these are limited by the IClientAPI outbound queues + /// anyway. + /// + /// It might be possible to ignore FetchInventory requests altogether, particularly as they are redundant wrt to + /// FetchInventoryDescendents requests, but this would require more investigation. + /// + public class AsyncInventorySender + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + /// + /// Queues fetch requests + /// + Queue m_fetchHolder = new Queue(); + + /// + /// Signal whether a queue is currently being processed or not. + /// + protected volatile bool m_processing; + + public AsyncInventorySender(Scene scene) + { + m_processing = false; + m_scene = scene; + } + + /// + /// Handle a fetch inventory request from the client + /// + /// + /// + /// + public void HandleFetchInventory(IClientAPI remoteClient, UUID itemID, UUID ownerID) + { + lock (m_fetchHolder) + { +// m_log.DebugFormat( +// "[ASYNC INVENTORY SENDER]: Putting request from {0} for {1} on queue", remoteClient.Name, itemID); + + m_fetchHolder.Enqueue(new FetchHolder(remoteClient, itemID)); + } + + if (!m_processing) + { + m_processing = true; + ProcessQueue(); + } + } + + /// + /// Process the queue of fetches + /// + protected void ProcessQueue() + { + FetchHolder fh = null; + + while (true) + { + lock (m_fetchHolder) + { +// m_log.DebugFormat("[ASYNC INVENTORY SENDER]: {0} items left to process", m_fetchHolder.Count); + + if (m_fetchHolder.Count == 0) + { + m_processing = false; + return; + } + else + { + fh = m_fetchHolder.Dequeue(); + } + } + + if (fh.Client.IsLoggingOut) + continue; + +// m_log.DebugFormat( +// "[ASYNC INVENTORY SENDER]: Handling request from {0} for {1} on queue", fh.Client.Name, fh.ItemID); + + InventoryItemBase item = new InventoryItemBase(fh.ItemID, fh.Client.AgentId); + item = m_scene.InventoryService.GetItem(item); + + if (item != null) + fh.Client.SendInventoryItemDetails(item.Owner, item); + + // TODO: Possibly log any failure + } + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index 13085e3838..30421d4224 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -51,6 +51,11 @@ namespace OpenSim.Region.Framework.Scenes /// protected AsyncSceneObjectGroupDeleter m_asyncSceneObjectDeleter; + /// + /// Allows inventory details to be sent to clients asynchronously + /// + protected AsyncInventorySender m_asyncInventorySender; + /// /// Start all the scripts in the scene which should be started. /// diff --git a/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs b/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs index e2d7208ff7..44472b2cb7 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs @@ -461,31 +461,6 @@ namespace OpenSim.Region.Framework.Scenes } ); } - - - /// - /// Handle a fetch inventory request from the client - /// - /// - /// - /// - public void HandleFetchInventory(IClientAPI remoteClient, UUID itemID, UUID ownerID) - { - if (LibraryService != null && LibraryService.LibraryRootFolder != null && ownerID == LibraryService.LibraryRootFolder.Owner) - { - //m_log.Debug("request info for library item"); - return; - } - - InventoryItemBase item = new InventoryItemBase(itemID, remoteClient.AgentId); - item = InventoryService.GetItem(item); - - if (item != null) - { - remoteClient.SendInventoryItemDetails(ownerID, item); - } - // else shouldn't we send an alert message? - } /// /// Tell the client about the various child items and folders contained in the requested folder. diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index ad41e88db5..eeb881fe12 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -583,6 +583,8 @@ namespace OpenSim.Region.Framework.Scenes m_asyncSceneObjectDeleter = new AsyncSceneObjectGroupDeleter(this); m_asyncSceneObjectDeleter.Enabled = true; + m_asyncInventorySender = new AsyncInventorySender(this); + #region Region Settings // Load region settings @@ -2760,14 +2762,13 @@ namespace OpenSim.Region.Framework.Scenes public virtual void SubscribeToClientInventoryEvents(IClientAPI client) { - client.OnLinkInventoryItem += HandleLinkInventoryItem; client.OnCreateNewInventoryFolder += HandleCreateInventoryFolder; client.OnUpdateInventoryFolder += HandleUpdateInventoryFolder; client.OnMoveInventoryFolder += HandleMoveInventoryFolder; // 2; //!! client.OnFetchInventoryDescendents += HandleFetchInventoryDescendents; client.OnPurgeInventoryDescendents += HandlePurgeInventoryDescendents; // 2; //!! - client.OnFetchInventory += HandleFetchInventory; + client.OnFetchInventory += m_asyncInventorySender.HandleFetchInventory; client.OnUpdateInventoryItem += UpdateInventoryItemAsset; client.OnCopyInventoryItem += CopyInventoryItem; client.OnMoveInventoryItem += MoveInventoryItem;