Under moderate or greater CPU load, pushing to the threadpool means that events such as EstablishAgentCommunication may be processed before EnableSimulator even though the simulator sends them in the reverse order. This triggers the logging of various errors and warnings by libomv. Handling these synchronously shouldn't have a huge impact since the number of events is not high and taking time here can only hold up event upload
371 lines
15 KiB
C#
371 lines
15 KiB
C#
/*
|
|
* Copyright (c) 2006-2014, openmetaverse.org
|
|
* All rights reserved.
|
|
*
|
|
* - 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.
|
|
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.Text;
|
|
using System.Threading;
|
|
using OpenMetaverse.Packets;
|
|
using OpenMetaverse.Messages.Linden;
|
|
using OpenMetaverse.Interfaces;
|
|
|
|
namespace OpenMetaverse
|
|
{
|
|
/// <summary>
|
|
/// Registers, unregisters, and fires events generated by incoming packets
|
|
/// </summary>
|
|
public class PacketEventDictionary
|
|
{
|
|
private sealed class PacketCallback
|
|
{
|
|
public EventHandler<PacketReceivedEventArgs> Callback;
|
|
public bool IsAsync;
|
|
|
|
public PacketCallback(EventHandler<PacketReceivedEventArgs> callback, bool isAsync)
|
|
{
|
|
Callback = callback;
|
|
IsAsync = isAsync;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Object that is passed to worker threads in the ThreadPool for
|
|
/// firing packet callbacks
|
|
/// </summary>
|
|
private struct PacketCallbackWrapper
|
|
{
|
|
/// <summary>Callback to fire for this packet</summary>
|
|
public EventHandler<PacketReceivedEventArgs> Callback;
|
|
/// <summary>Reference to the simulator that this packet came from</summary>
|
|
public Simulator Simulator;
|
|
/// <summary>The packet that needs to be processed</summary>
|
|
public Packet Packet;
|
|
}
|
|
|
|
/// <summary>Reference to the GridClient object</summary>
|
|
public GridClient Client;
|
|
|
|
private Dictionary<PacketType, PacketCallback> _EventTable = new Dictionary<PacketType, PacketCallback>();
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
/// <param name="client"></param>
|
|
public PacketEventDictionary(GridClient client)
|
|
{
|
|
Client = client;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register an event handler
|
|
/// </summary>
|
|
/// <remarks>Use PacketType.Default to fire this event on every
|
|
/// incoming packet</remarks>
|
|
/// <param name="packetType">Packet type to register the handler for</param>
|
|
/// <param name="eventHandler">Callback to be fired</param>
|
|
/// <param name="isAsync">True if this callback should be ran
|
|
/// asynchronously, false to run it synchronous</param>
|
|
public void RegisterEvent(PacketType packetType, EventHandler<PacketReceivedEventArgs> eventHandler, bool isAsync)
|
|
{
|
|
lock (_EventTable)
|
|
{
|
|
PacketCallback callback;
|
|
if (_EventTable.TryGetValue(packetType, out callback))
|
|
{
|
|
callback.Callback += eventHandler;
|
|
callback.IsAsync = callback.IsAsync || isAsync;
|
|
}
|
|
else
|
|
{
|
|
callback = new PacketCallback(eventHandler, isAsync);
|
|
_EventTable[packetType] = callback;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregister an event handler
|
|
/// </summary>
|
|
/// <param name="packetType">Packet type to unregister the handler for</param>
|
|
/// <param name="eventHandler">Callback to be unregistered</param>
|
|
public void UnregisterEvent(PacketType packetType, EventHandler<PacketReceivedEventArgs> eventHandler)
|
|
{
|
|
lock (_EventTable)
|
|
{
|
|
PacketCallback callback;
|
|
if (_EventTable.TryGetValue(packetType, out callback))
|
|
{
|
|
callback.Callback -= eventHandler;
|
|
if (callback.Callback == null || callback.Callback.GetInvocationList().Length == 0)
|
|
_EventTable.Remove(packetType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fire the events registered for this packet type
|
|
/// </summary>
|
|
/// <param name="packetType">Incoming packet type</param>
|
|
/// <param name="packet">Incoming packet</param>
|
|
/// <param name="simulator">Simulator this packet was received from</param>
|
|
internal void RaiseEvent(PacketType packetType, Packet packet, Simulator simulator)
|
|
{
|
|
PacketCallback callback;
|
|
|
|
// Default handler first, if one exists
|
|
if (_EventTable.TryGetValue(PacketType.Default, out callback) && callback.Callback != null)
|
|
{
|
|
if (callback.IsAsync)
|
|
{
|
|
PacketCallbackWrapper wrapper;
|
|
wrapper.Callback = callback.Callback;
|
|
wrapper.Packet = packet;
|
|
wrapper.Simulator = simulator;
|
|
WorkPool.QueueUserWorkItem(ThreadPoolDelegate, wrapper);
|
|
}
|
|
else
|
|
{
|
|
try { callback.Callback(this, new PacketReceivedEventArgs(packet, simulator)); }
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Default packet event handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_EventTable.TryGetValue(packetType, out callback) && callback.Callback != null)
|
|
{
|
|
if (callback.IsAsync)
|
|
{
|
|
PacketCallbackWrapper wrapper;
|
|
wrapper.Callback = callback.Callback;
|
|
wrapper.Packet = packet;
|
|
wrapper.Simulator = simulator;
|
|
WorkPool.QueueUserWorkItem(ThreadPoolDelegate, wrapper);
|
|
}
|
|
else
|
|
{
|
|
try { callback.Callback(this, new PacketReceivedEventArgs(packet, simulator)); }
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Packet event handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (packetType != PacketType.Default && packetType != PacketType.PacketAck)
|
|
{
|
|
Logger.DebugLog("No handler registered for packet event " + packetType, Client);
|
|
}
|
|
}
|
|
|
|
private void ThreadPoolDelegate(Object state)
|
|
{
|
|
PacketCallbackWrapper wrapper = (PacketCallbackWrapper)state;
|
|
|
|
try
|
|
{
|
|
wrapper.Callback(this, new PacketReceivedEventArgs(wrapper.Packet, wrapper.Simulator));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Async Packet Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers, unregisters, and fires events generated by the Capabilities
|
|
/// event queue
|
|
/// </summary>
|
|
public class CapsEventDictionary
|
|
{
|
|
/// <summary>
|
|
/// Object that is passed to worker threads in the ThreadPool for
|
|
/// firing CAPS callbacks
|
|
/// </summary>
|
|
private struct CapsCallbackWrapper
|
|
{
|
|
/// <summary>Callback to fire for this packet</summary>
|
|
public Caps.EventQueueCallback Callback;
|
|
/// <summary>Name of the CAPS event</summary>
|
|
public string CapsEvent;
|
|
/// <summary>Strongly typed decoded data</summary>
|
|
public IMessage Message;
|
|
/// <summary>Reference to the simulator that generated this event</summary>
|
|
public Simulator Simulator;
|
|
}
|
|
|
|
/// <summary>Reference to the GridClient object</summary>
|
|
public GridClient Client;
|
|
|
|
private Dictionary<string, Caps.EventQueueCallback> _EventTable =
|
|
new Dictionary<string, Caps.EventQueueCallback>();
|
|
private WaitCallback _ThreadPoolCallback;
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
/// <param name="client">Reference to the GridClient object</param>
|
|
public CapsEventDictionary(GridClient client)
|
|
{
|
|
Client = client;
|
|
_ThreadPoolCallback = new WaitCallback(ThreadPoolDelegate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register an new event handler for a capabilities event sent via the EventQueue
|
|
/// </summary>
|
|
/// <remarks>Use String.Empty to fire this event on every CAPS event</remarks>
|
|
/// <param name="capsEvent">Capability event name to register the
|
|
/// handler for</param>
|
|
/// <param name="eventHandler">Callback to fire</param>
|
|
public void RegisterEvent(string capsEvent, Caps.EventQueueCallback eventHandler)
|
|
{
|
|
// TODO: Should we add support for synchronous CAPS handlers?
|
|
lock (_EventTable)
|
|
{
|
|
if (_EventTable.ContainsKey(capsEvent))
|
|
_EventTable[capsEvent] += eventHandler;
|
|
else
|
|
_EventTable[capsEvent] = eventHandler;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregister a previously registered capabilities handler
|
|
/// </summary>
|
|
/// <param name="capsEvent">Capability event name unregister the
|
|
/// handler for</param>
|
|
/// <param name="eventHandler">Callback to unregister</param>
|
|
public void UnregisterEvent(string capsEvent, Caps.EventQueueCallback eventHandler)
|
|
{
|
|
lock (_EventTable)
|
|
{
|
|
if (_EventTable.ContainsKey(capsEvent) && _EventTable[capsEvent] != null)
|
|
_EventTable[capsEvent] -= eventHandler;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fire the events registered for this event type synchronously
|
|
/// </summary>
|
|
/// <param name="capsEvent">Capability name</param>
|
|
/// <param name="message">Decoded event body</param>
|
|
/// <param name="simulator">Reference to the simulator that
|
|
/// generated this event</param>
|
|
internal void RaiseEvent(string capsEvent, IMessage message, Simulator simulator)
|
|
{
|
|
bool specialHandler = false;
|
|
Caps.EventQueueCallback callback;
|
|
|
|
// Default handler first, if one exists
|
|
if (_EventTable.TryGetValue(capsEvent, out callback))
|
|
{
|
|
if (callback != null)
|
|
{
|
|
try { callback(capsEvent, message, simulator); }
|
|
catch (Exception ex) { Logger.Log("CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client); }
|
|
}
|
|
}
|
|
|
|
// Explicit handler next
|
|
if (_EventTable.TryGetValue(capsEvent, out callback) && callback != null)
|
|
{
|
|
try { callback(capsEvent, message, simulator); }
|
|
catch (Exception ex) { Logger.Log("CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client); }
|
|
|
|
specialHandler = true;
|
|
}
|
|
|
|
if (!specialHandler)
|
|
Logger.Log("Unhandled CAPS event " + capsEvent, Helpers.LogLevel.Warning, Client);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fire the events registered for this event type asynchronously
|
|
/// </summary>
|
|
/// <param name="capsEvent">Capability name</param>
|
|
/// <param name="message">Decoded event body</param>
|
|
/// <param name="simulator">Reference to the simulator that
|
|
/// generated this event</param>
|
|
internal void BeginRaiseEvent(string capsEvent, IMessage message, Simulator simulator)
|
|
{
|
|
bool specialHandler = false;
|
|
Caps.EventQueueCallback callback;
|
|
|
|
// Default handler first, if one exists
|
|
if (_EventTable.TryGetValue(String.Empty, out callback))
|
|
{
|
|
if (callback != null)
|
|
{
|
|
callback(capsEvent, message, simulator);
|
|
// CapsCallbackWrapper wrapper;
|
|
// wrapper.Callback = callback;
|
|
// wrapper.CapsEvent = capsEvent;
|
|
// wrapper.Message = message;
|
|
// wrapper.Simulator = simulator;
|
|
// WorkPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
|
|
}
|
|
}
|
|
|
|
// Explicit handler next
|
|
if (_EventTable.TryGetValue(capsEvent, out callback) && callback != null)
|
|
{
|
|
callback(capsEvent, message, simulator);
|
|
|
|
// CapsCallbackWrapper wrapper;
|
|
// wrapper.Callback = callback;
|
|
// wrapper.CapsEvent = capsEvent;
|
|
// wrapper.Message = message;
|
|
// wrapper.Simulator = simulator;
|
|
// WorkPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
|
|
|
|
specialHandler = true;
|
|
}
|
|
|
|
if (!specialHandler)
|
|
Logger.Log("Unhandled CAPS event " + capsEvent, Helpers.LogLevel.Warning, Client);
|
|
}
|
|
|
|
private void ThreadPoolDelegate(Object state)
|
|
{
|
|
CapsCallbackWrapper wrapper = (CapsCallbackWrapper)state;
|
|
|
|
try
|
|
{
|
|
wrapper.Callback(wrapper.CapsEvent, wrapper.Message, wrapper.Simulator);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log("Async CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
|
}
|
|
}
|
|
}
|
|
}
|