/*
* 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 OpenSim 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.Threading;
using libsecondlife;
using OpenSim.Framework;
using OpenSim.Region.Environment.Scenes.Scripting;
namespace OpenSim.Region.ScriptEngine.Common.ScriptEngineBase
{
///
/// EventQueueManager handles event queues
/// Events are queued and executed in separate thread
///
[Serializable]
public class EventQueueManager
{
//
// Class is instanced in "ScriptEngine" and used by "EventManager" also instanced in "ScriptEngine".
//
// Class purpose is to queue and execute functions that are received by "EventManager":
// - allowing "EventManager" to release its event thread immediately, thus not interrupting server execution.
// - allowing us to prioritize and control execution of script functions.
// Class can use multiple threads for simultaneous execution. Mutexes are used for thread safety.
//
// 1. Hold an execution queue for scripts
// 2. Use threads to process queue, each thread executes one script function on each pass.
// 3. Catch any script error and process it
//
//
// Notes:
// * Current execution load balancing is optimized for 1 thread, and can cause unfair execute balancing between scripts.
// Not noticeable unless server is under high load.
// * This class contains the number of threads used for script executions. Since we are not microthreading scripts yet,
// increase number of threads to allow more concurrent script executions in OpenSim.
//
///
/// List of threads processing event queue
///
private List eventQueueThreads;// = new List();
private object eventQueueThreadsLock;// = new object();
private static List staticEventQueueThreads;// = new List();
private static object staticEventQueueThreadsLock;// = new object();
public object queueLock = new object(); // Mutex lock object
///
/// How many threads to process queue with
///
private int numberOfThreads;
///
/// Maximum time one function can use for execution before we perform a thread kill
///
private int maxFunctionExecutionTimems;
private bool EnforceMaxExecutionTime;
///
/// Queue containing events waiting to be executed
///
public Queue eventQueue = new Queue();
///
/// Queue item structure
///
public struct QueueItemStruct
{
public uint localID;
public LLUUID itemID;
public string functionName;
public Queue_llDetectParams_Struct llDetectParams;
public object[] param;
}
///
/// Shared empty llDetectNull
///
public readonly static Queue_llDetectParams_Struct llDetectNull = new Queue_llDetectParams_Struct();
///
/// Structure to hold data for llDetect* commands
///
[Serializable]
public struct Queue_llDetectParams_Struct
{
// More or less just a placeholder for the actual moving of additional data
// should be fixed to something better :)
public LSL_Types.key[] _key;
public LSL_Types.Quaternion[] _Quaternion;
public LSL_Types.Vector3[] _Vector3;
public bool[] _bool;
public int[] _int;
public string[] _string;
}
///
/// List of localID locks for mutex processing of script events
///
private List objectLocks = new List();
private object tryLockLock = new object(); // Mutex lock object
public ScriptEngine m_ScriptEngine;
public Thread ExecutionTimeoutEnforcingThread;
public EventQueueManager(ScriptEngine _ScriptEngine)
{
m_ScriptEngine = _ScriptEngine;
// Create thread pool list and lock object
// Determine from config if threads should be dedicated to regions or shared
if (m_ScriptEngine.ScriptConfigSource.GetBoolean("PrivateRegionThreads", false))
{
// PRIVATE THREAD POOL PER REGION
eventQueueThreads = new List();
eventQueueThreadsLock = new object();
}
else
{
// SHARED THREAD POOL
// Crate the objects in statics
if (staticEventQueueThreads == null)
staticEventQueueThreads = new List();
if (staticEventQueueThreadsLock == null)
staticEventQueueThreadsLock = new object();
// Create local reference to them
eventQueueThreads = staticEventQueueThreads;
eventQueueThreadsLock = staticEventQueueThreadsLock;
}
numberOfThreads = m_ScriptEngine.ScriptConfigSource.GetInt("NumberOfScriptThreads", 2);
maxFunctionExecutionTimems = m_ScriptEngine.ScriptConfigSource.GetInt("MaxEventExecutionTimeMs", 5000);
EnforceMaxExecutionTime = m_ScriptEngine.ScriptConfigSource.GetBoolean("EnforceMaxEventExecutionTime", false);
// Start function max exec time enforcement thread
if (EnforceMaxExecutionTime)
{
ExecutionTimeoutEnforcingThread = new Thread(ExecutionTimeoutEnforcingLoop);
ExecutionTimeoutEnforcingThread.Name = "ExecutionTimeoutEnforcingThread";
ExecutionTimeoutEnforcingThread.IsBackground = true;
ExecutionTimeoutEnforcingThread.Start();
}
//
// Start event queue processing threads (worker threads)
//
lock (eventQueueThreadsLock)
{
for (int ThreadCount = eventQueueThreads.Count; ThreadCount < numberOfThreads; ThreadCount++)
{
StartNewThreadClass();
}
}
}
~EventQueueManager()
{
try
{
if (ExecutionTimeoutEnforcingThread != null)
{
if (ExecutionTimeoutEnforcingThread.IsAlive)
{
ExecutionTimeoutEnforcingThread.Abort();
}
}
}
catch (Exception ex)
{
}
// Kill worker threads
lock (eventQueueThreadsLock)
{
foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
{
EventQueueThread.Shutdown();
}
eventQueueThreads.Clear();
}
// Todo: Clean up our queues
eventQueue.Clear();
}
///
/// Try to get a mutex lock on localID
///
///
///
public bool TryLock(uint localID)
{
lock (tryLockLock)
{
if (objectLocks.Contains(localID) == true)
{
return false;
}
else
{
objectLocks.Add(localID);
return true;
}
}
}
///
/// Release mutex lock on localID
///
///
public void ReleaseLock(uint localID)
{
lock (tryLockLock)
{
if (objectLocks.Contains(localID) == true)
{
objectLocks.Remove(localID);
}
}
}
///
/// Add event to event execution queue
///
/// Region object ID
/// Name of the function, will be state + "_event_" + FunctionName
/// Array of parameters to match event mask
public void AddToObjectQueue(uint localID, string FunctionName, Queue_llDetectParams_Struct qParams, params object[] param)
{
// Determine all scripts in Object and add to their queue
//myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Adding localID: " + localID + ", FunctionName: " + FunctionName);
// Do we have any scripts in this object at all? If not, return
if (m_ScriptEngine.m_ScriptManager.Scripts.ContainsKey(localID) == false)
{
//Console.WriteLine("Event \String.Empty + FunctionName + "\" for localID: " + localID + ". No scripts found on this localID.");
return;
}
Dictionary.KeyCollection scriptKeys =
m_ScriptEngine.m_ScriptManager.GetScriptKeys(localID);
foreach (LLUUID itemID in scriptKeys)
{
// Add to each script in that object
// TODO: Some scripts may not subscribe to this event. Should we NOT add it? Does it matter?
AddToScriptQueue(localID, itemID, FunctionName, qParams, param);
}
}
///
/// Add event to event execution queue
///
/// Region object ID
/// Region script ID
/// Name of the function, will be state + "_event_" + FunctionName
/// Array of parameters to match event mask
public void AddToScriptQueue(uint localID, LLUUID itemID, string FunctionName, Queue_llDetectParams_Struct qParams, params object[] param)
{
lock (queueLock)
{
// Create a structure and add data
QueueItemStruct QIS = new QueueItemStruct();
QIS.localID = localID;
QIS.itemID = itemID;
QIS.functionName = FunctionName;
QIS.llDetectParams = qParams;
QIS.param = param;
// Add it to queue
eventQueue.Enqueue(QIS);
}
}
///
/// A thread should run in this loop and check all running scripts
///
public void ExecutionTimeoutEnforcingLoop()
{
try
{
while (true)
{
System.Threading.Thread.Sleep(maxFunctionExecutionTimems);
lock (eventQueueThreadsLock)
{
foreach (EventQueueThreadClass EventQueueThread in new ArrayList(eventQueueThreads))
{
if (EventQueueThread.InExecution)
{
if (DateTime.Now.Subtract(EventQueueThread.LastExecutionStarted).Milliseconds >
maxFunctionExecutionTimems)
{
// We need to kill this thread!
AbortThreadClass(EventQueueThread);
// Then start another
StartNewThreadClass();
}
}
}
}
}
}
catch (ThreadAbortException tae)
{
}
}
private void AbortThreadClass(EventQueueThreadClass threadClass)
{
try
{
threadClass.Shutdown();
}
catch (Exception ex)
{
Console.WriteLine("Could you please report this to Tedd:");
Console.WriteLine("Script thread execution timeout kill ended in exception: " + ex.ToString());
}
m_ScriptEngine.Log.Debug("DotNetEngine", "Killed script execution thread, count: " + eventQueueThreads.Count);
}
private void StartNewThreadClass()
{
EventQueueThreadClass eqtc = new EventQueueThreadClass(this);
eventQueueThreads.Add(eqtc);
m_ScriptEngine.Log.Debug("DotNetEngine", "Started new script execution thread, count: " + eventQueueThreads.Count);
}
}
}