From 134f86e8d5c414409631b25b8c6f0ee45fbd8631 Mon Sep 17 00:00:00 2001
From: David Walter Seikel
Date: Thu, 3 Nov 2016 21:44:39 +1000
Subject: Initial update to OpenSim 0.8.2.1 source code.
---
OpenSim/Framework/Util.cs | 1512 +++++++++++++++++++++++++++++++++++++--------
1 file changed, 1247 insertions(+), 265 deletions(-)
(limited to 'OpenSim/Framework/Util.cs')
diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs
index 0bd2977..1f74168 100644
--- a/OpenSim/Framework/Util.cs
+++ b/OpenSim/Framework/Util.cs
@@ -45,14 +45,33 @@ using System.Text.RegularExpressions;
using System.Xml;
using System.Threading;
using log4net;
+using log4net.Appender;
using Nini.Config;
using Nwc.XmlRpc;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using Amib.Threading;
+using System.Collections.Concurrent;
+using System.Collections.Specialized;
+using System.Web;
namespace OpenSim.Framework
{
+ [Flags]
+ public enum PermissionMask : uint
+ {
+ None = 0,
+ Transfer = 1 << 13,
+ Modify = 1 << 14,
+ Copy = 1 << 15,
+ Export = 1 << 16,
+ Move = 1 << 19,
+ Damage = 1 << 20,
+ // All does not contain Export, which is special and must be
+ // explicitly given
+ All = (1 << 13) | (1 << 14) | (1 << 15) | (1 << 19)
+ }
+
///
/// The method used by Util.FireAndForget for asynchronously firing events
///
@@ -73,14 +92,53 @@ namespace OpenSim.Framework
}
///
+ /// Class for delivering SmartThreadPool statistical information
+ ///
+ ///
+ /// We do it this way so that we do not directly expose STP.
+ ///
+ public class STPInfo
+ {
+ public string Name { get; set; }
+ public STPStartInfo STPStartInfo { get; set; }
+ public WIGStartInfo WIGStartInfo { get; set; }
+ public bool IsIdle { get; set; }
+ public bool IsShuttingDown { get; set; }
+ public int MaxThreads { get; set; }
+ public int MinThreads { get; set; }
+ public int InUseThreads { get; set; }
+ public int ActiveThreads { get; set; }
+ public int WaitingCallbacks { get; set; }
+ public int MaxConcurrentWorkItems { get; set; }
+ }
+
+ ///
/// Miscellaneous utility functions
///
- public class Util
+ public static class Util
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+ ///
+ /// Log-level for the thread pool:
+ /// 0 = no logging
+ /// 1 = only first line of stack trace; don't log common threads
+ /// 2 = full stack trace; don't log common threads
+ /// 3 = full stack trace, including common threads
+ ///
+ public static int LogThreadPool { get; set; }
+ public static bool LogOverloads { get; set; }
+
+ public static readonly int MAX_THREADPOOL_LEVEL = 3;
+
+ static Util()
+ {
+ LogThreadPool = 0;
+ LogOverloads = true;
+ }
+
private static uint nextXferID = 5000;
- private static Random randomClass = new Random();
+ private static Random randomClass = new ThreadSafeRandom();
// Get a list of invalid file characters (OS dependent)
private static string regexInvalidFileChars = "[" + new String(Path.GetInvalidFileNameChars()) + "]";
@@ -92,8 +150,11 @@ namespace OpenSim.Framework
///
private static SmartThreadPool m_ThreadPool;
+ // Watchdog timer that aborts threads that have timed-out
+ private static Timer m_threadPoolWatchdog;
+
// Unix-epoch starts at January 1st 1970, 00:00:00 UTC. And all our times in the server are (or at least should be) in UTC.
- private static readonly DateTime unixEpoch =
+ public static readonly DateTime UnixEpoch =
DateTime.ParseExact("1970-01-01 00:00:00 +0", "yyyy-MM-dd hh:mm:ss z", DateTimeFormatInfo.InvariantInfo).ToUniversalTime();
private static readonly string rawUUIDPattern
@@ -104,6 +165,11 @@ namespace OpenSim.Framework
public static FireAndForgetMethod DefaultFireAndForgetMethod = FireAndForgetMethod.SmartThreadPool;
public static FireAndForgetMethod FireAndForgetMethod = DefaultFireAndForgetMethod;
+ public static bool IsPlatformMono
+ {
+ get { return Type.GetType("Mono.Runtime") != null; }
+ }
+
///
/// Gets the name of the directory where the current running executable
/// is located
@@ -291,6 +357,49 @@ namespace OpenSim.Framework
return Utils.UIntsToLong(X, Y);
}
+ // Regions are identified with a 'handle' made up of its region coordinates packed into a ulong.
+ // Several places rely on the ability to extract a region's location from its handle.
+ // Note the location is in 'world coordinates' (see below).
+ // Region handles are based on the lowest coordinate of the region so trim the passed x,y to be the regions 0,0.
+ public static ulong RegionWorldLocToHandle(uint X, uint Y)
+ {
+ return Utils.UIntsToLong(X, Y);
+ }
+
+ public static ulong RegionLocToHandle(uint X, uint Y)
+ {
+ return Utils.UIntsToLong(Util.RegionToWorldLoc(X), Util.RegionToWorldLoc(Y));
+ }
+
+ public static void RegionHandleToWorldLoc(ulong handle, out uint X, out uint Y)
+ {
+ X = (uint)(handle >> 32);
+ Y = (uint)(handle & (ulong)uint.MaxValue);
+ }
+
+ public static void RegionHandleToRegionLoc(ulong handle, out uint X, out uint Y)
+ {
+ uint worldX, worldY;
+ RegionHandleToWorldLoc(handle, out worldX, out worldY);
+ X = WorldToRegionLoc(worldX);
+ Y = WorldToRegionLoc(worldY);
+ }
+
+ // A region location can be 'world coordinates' (meters from zero) or 'region coordinates'
+ // (number of regions from zero). This measurement of regions relies on the legacy 256 region size.
+ // These routines exist to make what is being converted explicit so the next person knows what was meant.
+ // Convert a region's 'world coordinate' to its 'region coordinate'.
+ public static uint WorldToRegionLoc(uint worldCoord)
+ {
+ return worldCoord / Constants.RegionSize;
+ }
+
+ // Convert a region's 'region coordinate' to its 'world coordinate'.
+ public static uint RegionToWorldLoc(uint regionCoord)
+ {
+ return regionCoord * Constants.RegionSize;
+ }
+
public static T Clamp(T x, T min, T max)
where T : IComparable
{
@@ -302,15 +411,31 @@ namespace OpenSim.Framework
// Clamp the maximum magnitude of a vector
public static Vector3 ClampV(Vector3 x, float max)
{
- Vector3 ret = x;
float lenSq = x.LengthSquared();
if (lenSq > (max * max))
{
x = x / x.Length() * max;
}
+
return x;
}
+ ///
+ /// Check if any of the values in a Vector3 are NaN or Infinity
+ ///
+ /// Vector3 to check
+ ///
+ public static bool IsNanOrInfinity(Vector3 v)
+ {
+ if (float.IsNaN(v.X) || float.IsNaN(v.Y) || float.IsNaN(v.Z))
+ return true;
+
+ if (float.IsInfinity(v.X) || float.IsInfinity(v.Y) || float.IsNaN(v.Z))
+ return true;
+
+ return false;
+ }
+
// Inclusive, within range test (true if equal to the endpoints)
public static bool InRange(T x, T min, T max)
where T : IComparable
@@ -400,6 +525,19 @@ namespace OpenSim.Framework
return sb.ToString();
}
+ public static byte[] DocToBytes(XmlDocument doc)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ using (XmlTextWriter xw = new XmlTextWriter(ms, null))
+ {
+ xw.Formatting = Formatting.Indented;
+ doc.WriteTo(xw);
+ xw.Flush();
+
+ return ms.ToArray();
+ }
+ }
+
///
/// Is the platform Windows?
///
@@ -416,6 +554,11 @@ namespace OpenSim.Framework
public static bool LoadArchSpecificWindowsDll(string libraryName)
{
+ return LoadArchSpecificWindowsDll(libraryName, string.Empty);
+ }
+
+ public static bool LoadArchSpecificWindowsDll(string libraryName, string path)
+ {
// We do this so that OpenSimulator on Windows loads the correct native library depending on whether
// it's running as a 32-bit process or a 64-bit one. By invoking LoadLibary here, later DLLImports
// will find it already loaded later on.
@@ -425,9 +568,9 @@ namespace OpenSim.Framework
string nativeLibraryPath;
if (Util.Is64BitProcess())
- nativeLibraryPath = "lib64/" + libraryName;
+ nativeLibraryPath = Path.Combine(Path.Combine(path, "lib64"), libraryName);
else
- nativeLibraryPath = "lib32/" + libraryName;
+ nativeLibraryPath = Path.Combine(Path.Combine(path, "lib32"), libraryName);
m_log.DebugFormat("[UTIL]: Loading native Windows library at {0}", nativeLibraryPath);
@@ -479,20 +622,18 @@ namespace OpenSim.Framework
public static int ToUnixTime(DateTime stamp)
{
- TimeSpan t = stamp.ToUniversalTime() - unixEpoch;
- return (int) t.TotalSeconds;
+ TimeSpan t = stamp.ToUniversalTime() - UnixEpoch;
+ return (int)t.TotalSeconds;
}
public static DateTime ToDateTime(ulong seconds)
{
- DateTime epoch = unixEpoch;
- return epoch.AddSeconds(seconds);
+ return UnixEpoch.AddSeconds(seconds);
}
public static DateTime ToDateTime(int seconds)
{
- DateTime epoch = unixEpoch;
- return epoch.AddSeconds(seconds);
+ return UnixEpoch.AddSeconds(seconds);
}
///
@@ -664,16 +805,6 @@ namespace OpenSim.Framework
}
///
- /// Converts a URL to a IPAddress
- ///
- /// URL Standard Format
- /// A resolved IP Address
- public static IPAddress GetHostFromURL(string url)
- {
- return GetHostFromDNS(url.Split(new char[] {'/', ':'})[3]);
- }
-
- ///
/// Returns a IP address from a specified DNS, favouring IPv4 addresses.
///
/// DNS Hostname
@@ -763,6 +894,54 @@ namespace OpenSim.Framework
}
///
+ /// Parses a foreign asset ID.
+ ///
+ /// A possibly-foreign asset ID: http://grid.example.com:8002/00000000-0000-0000-0000-000000000000
+ /// The URL: http://grid.example.com:8002
+ /// The asset ID: 00000000-0000-0000-0000-000000000000. Returned even if 'id' isn't foreign.
+ /// True: this is a foreign asset ID; False: it isn't
+ public static bool ParseForeignAssetID(string id, out string url, out string assetID)
+ {
+ url = String.Empty;
+ assetID = String.Empty;
+
+ UUID uuid;
+ if (UUID.TryParse(id, out uuid))
+ {
+ assetID = uuid.ToString();
+ return false;
+ }
+
+ if ((id.Length == 0) || (id[0] != 'h' && id[0] != 'H'))
+ return false;
+
+ Uri assetUri;
+ if (!Uri.TryCreate(id, UriKind.Absolute, out assetUri) || assetUri.Scheme != Uri.UriSchemeHttp)
+ return false;
+
+ // Simian
+ if (assetUri.Query != string.Empty)
+ {
+ NameValueCollection qscoll = HttpUtility.ParseQueryString(assetUri.Query);
+ assetID = qscoll["id"];
+ if (assetID != null)
+ url = id.Replace(assetID, ""); // Malformed again, as simian expects
+ else
+ url = id; // !!! best effort
+ }
+ else // robust
+ {
+ url = "http://" + assetUri.Authority;
+ assetID = assetUri.LocalPath.Trim(new char[] { '/' });
+ }
+
+ if (!UUID.TryParse(assetID, out uuid))
+ return false;
+
+ return true;
+ }
+
+ ///
/// Removes all invalid path chars (OS dependent)
///
/// path
@@ -816,9 +995,22 @@ namespace OpenSim.Framework
return ".";
}
+ public static string logFile()
+ {
+ foreach (IAppender appender in LogManager.GetRepository().GetAppenders())
+ {
+ if (appender is FileAppender)
+ {
+ return ((FileAppender)appender).File;
+ }
+ }
+
+ return "./OpenSim.log";
+ }
+
public static string logDir()
{
- return ".";
+ return Path.GetDirectoryName(logFile());
}
// From: http://coercedcode.blogspot.com/2008/03/c-generate-unique-filenames-within.html
@@ -849,12 +1041,13 @@ namespace OpenSim.Framework
return FileName;
}
- // Nini (config) related Methods
+ #region Nini (config) related Methods
+
public static IConfigSource ConvertDataRowToXMLConfig(DataRow row, string fileName)
{
if (!File.Exists(fileName))
{
- //create new file
+ // create new file
}
XmlConfigSource config = new XmlConfigSource(fileName);
AddDataRowToConfig(config, row);
@@ -872,6 +1065,202 @@ namespace OpenSim.Framework
}
}
+ ///
+ /// Gets the value of a configuration variable by looking into
+ /// multiple sections in order. The latter sections overwrite
+ /// any values previously found.
+ ///
+ /// Type of the variable
+ /// The configuration object
+ /// The configuration variable
+ /// Ordered sequence of sections to look at
+ ///
+ public static T GetConfigVarFromSections(IConfigSource config, string varname, string[] sections)
+ {
+ return GetConfigVarFromSections(config, varname, sections, default(T));
+ }
+
+ ///
+ /// Gets the value of a configuration variable by looking into
+ /// multiple sections in order. The latter sections overwrite
+ /// any values previously found.
+ ///
+ ///
+ /// If no value is found then the given default value is returned
+ ///
+ /// Type of the variable
+ /// The configuration object
+ /// The configuration variable
+ /// Ordered sequence of sections to look at
+ /// Default value
+ ///
+ public static T GetConfigVarFromSections(IConfigSource config, string varname, string[] sections, object val)
+ {
+ foreach (string section in sections)
+ {
+ IConfig cnf = config.Configs[section];
+ if (cnf == null)
+ continue;
+
+ if (typeof(T) == typeof(String))
+ val = cnf.GetString(varname, (string)val);
+ else if (typeof(T) == typeof(Boolean))
+ val = cnf.GetBoolean(varname, (bool)val);
+ else if (typeof(T) == typeof(Int32))
+ val = cnf.GetInt(varname, (int)val);
+ else if (typeof(T) == typeof(float))
+ val = cnf.GetFloat(varname, (float)val);
+ else
+ m_log.ErrorFormat("[UTIL]: Unhandled type {0}", typeof(T));
+ }
+
+ return (T)val;
+ }
+
+ public static void MergeEnvironmentToConfig(IConfigSource ConfigSource)
+ {
+ IConfig enVars = ConfigSource.Configs["Environment"];
+ // if section does not exist then user isn't expecting them, so don't bother.
+ if( enVars != null )
+ {
+ // load the values from the environment
+ EnvConfigSource envConfigSource = new EnvConfigSource();
+ // add the requested keys
+ string[] env_keys = enVars.GetKeys();
+ foreach ( string key in env_keys )
+ {
+ envConfigSource.AddEnv(key, string.Empty);
+ }
+ // load the values from environment
+ envConfigSource.LoadEnv();
+ // add them in to the master
+ ConfigSource.Merge(envConfigSource);
+ ConfigSource.ExpandKeyValues();
+ }
+ }
+
+ public static T ReadSettingsFromIniFile(IConfig config, T settingsClass)
+ {
+ Type settingsType = settingsClass.GetType();
+
+ FieldInfo[] fieldInfos = settingsType.GetFields();
+ foreach (FieldInfo fieldInfo in fieldInfos)
+ {
+ if (!fieldInfo.IsStatic)
+ {
+ if (fieldInfo.FieldType == typeof(System.String))
+ {
+ fieldInfo.SetValue(settingsClass, config.Get(fieldInfo.Name, (string)fieldInfo.GetValue(settingsClass)));
+ }
+ else if (fieldInfo.FieldType == typeof(System.Boolean))
+ {
+ fieldInfo.SetValue(settingsClass, config.GetBoolean(fieldInfo.Name, (bool)fieldInfo.GetValue(settingsClass)));
+ }
+ else if (fieldInfo.FieldType == typeof(System.Int32))
+ {
+ fieldInfo.SetValue(settingsClass, config.GetInt(fieldInfo.Name, (int)fieldInfo.GetValue(settingsClass)));
+ }
+ else if (fieldInfo.FieldType == typeof(System.Single))
+ {
+ fieldInfo.SetValue(settingsClass, config.GetFloat(fieldInfo.Name, (float)fieldInfo.GetValue(settingsClass)));
+ }
+ else if (fieldInfo.FieldType == typeof(System.UInt32))
+ {
+ fieldInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(fieldInfo.Name, ((uint)fieldInfo.GetValue(settingsClass)).ToString())));
+ }
+ }
+ }
+
+ PropertyInfo[] propertyInfos = settingsType.GetProperties();
+ foreach (PropertyInfo propInfo in propertyInfos)
+ {
+ if ((propInfo.CanRead) && (propInfo.CanWrite))
+ {
+ if (propInfo.PropertyType == typeof(System.String))
+ {
+ propInfo.SetValue(settingsClass, config.Get(propInfo.Name, (string)propInfo.GetValue(settingsClass, null)), null);
+ }
+ else if (propInfo.PropertyType == typeof(System.Boolean))
+ {
+ propInfo.SetValue(settingsClass, config.GetBoolean(propInfo.Name, (bool)propInfo.GetValue(settingsClass, null)), null);
+ }
+ else if (propInfo.PropertyType == typeof(System.Int32))
+ {
+ propInfo.SetValue(settingsClass, config.GetInt(propInfo.Name, (int)propInfo.GetValue(settingsClass, null)), null);
+ }
+ else if (propInfo.PropertyType == typeof(System.Single))
+ {
+ propInfo.SetValue(settingsClass, config.GetFloat(propInfo.Name, (float)propInfo.GetValue(settingsClass, null)), null);
+ }
+ if (propInfo.PropertyType == typeof(System.UInt32))
+ {
+ propInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(propInfo.Name, ((uint)propInfo.GetValue(settingsClass, null)).ToString())), null);
+ }
+ }
+ }
+
+ return settingsClass;
+ }
+
+ ///
+ /// Reads a configuration file, configFile, merging it with the main configuration, config.
+ /// If the file doesn't exist, it copies a given exampleConfigFile onto configFile, and then
+ /// merges it.
+ ///
+ /// The main configuration data
+ /// The name of a configuration file in ConfigDirectory variable, no path
+ /// Full path to an example configuration file
+ /// Full path ConfigDirectory/configFileName
+ /// True if the file was created in ConfigDirectory, false if it existed
+ /// True if success
+ public static bool MergeConfigurationFile(IConfigSource config, string configFileName, string exampleConfigFile, out string configFilePath, out bool created)
+ {
+ created = false;
+ configFilePath = string.Empty;
+
+ IConfig cnf = config.Configs["Startup"];
+ if (cnf == null)
+ {
+ m_log.WarnFormat("[UTILS]: Startup section doesn't exist");
+ return false;
+ }
+
+ string configDirectory = cnf.GetString("ConfigDirectory", ".");
+ string configFile = Path.Combine(configDirectory, configFileName);
+
+ if (!File.Exists(configFile) && !string.IsNullOrEmpty(exampleConfigFile))
+ {
+ // We need to copy the example onto it
+
+ if (!Directory.Exists(configDirectory))
+ Directory.CreateDirectory(configDirectory);
+
+ try
+ {
+ File.Copy(exampleConfigFile, configFile);
+ created = true;
+ }
+ catch (Exception e)
+ {
+ m_log.WarnFormat("[UTILS]: Exception copying configuration file {0} to {1}: {2}", configFile, exampleConfigFile, e.Message);
+ return false;
+ }
+ }
+
+ if (File.Exists(configFile))
+ {
+ // Merge
+ config.Merge(new IniConfigSource(configFile));
+ config.ExpandKeyValues();
+ configFilePath = configFile;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ #endregion
+
public static float Clip(float x, float min, float max)
{
return Math.Min(Math.Max(x, min), max);
@@ -999,46 +1388,6 @@ namespace OpenSim.Framework
return ret;
}
- public static string Compress(string text)
- {
- byte[] buffer = Util.UTF8.GetBytes(text);
- MemoryStream memory = new MemoryStream();
- using (GZipStream compressor = new GZipStream(memory, CompressionMode.Compress, true))
- {
- compressor.Write(buffer, 0, buffer.Length);
- }
-
- memory.Position = 0;
-
- byte[] compressed = new byte[memory.Length];
- memory.Read(compressed, 0, compressed.Length);
-
- byte[] compressedBuffer = new byte[compressed.Length + 4];
- Buffer.BlockCopy(compressed, 0, compressedBuffer, 4, compressed.Length);
- Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, compressedBuffer, 0, 4);
- return Convert.ToBase64String(compressedBuffer);
- }
-
- public static string Decompress(string compressedText)
- {
- byte[] compressedBuffer = Convert.FromBase64String(compressedText);
- using (MemoryStream memory = new MemoryStream())
- {
- int msgLength = BitConverter.ToInt32(compressedBuffer, 0);
- memory.Write(compressedBuffer, 4, compressedBuffer.Length - 4);
-
- byte[] buffer = new byte[msgLength];
-
- memory.Position = 0;
- using (GZipStream decompressor = new GZipStream(memory, CompressionMode.Decompress))
- {
- decompressor.Read(buffer, 0, buffer.Length);
- }
-
- return Util.UTF8.GetString(buffer);
- }
- }
-
///
/// Copy data from one stream to another, leaving the read position of both streams at the beginning.
///
@@ -1119,7 +1468,7 @@ namespace OpenSim.Framework
byte[] bytes =
{
(byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24),
- (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56),
+ (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle >> 56),
(byte)x, (byte)(x >> 8), 0, 0,
(byte)y, (byte)(y >> 8), 0, 0 };
return new UUID(bytes, 0);
@@ -1130,7 +1479,7 @@ namespace OpenSim.Framework
byte[] bytes =
{
(byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24),
- (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56),
+ (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle >> 56),
(byte)x, (byte)(x >> 8), (byte)z, (byte)(z >> 8),
(byte)y, (byte)(y >> 8), 0, 0 };
return new UUID(bytes, 0);
@@ -1203,7 +1552,7 @@ namespace OpenSim.Framework
ru = "OSX/Mono";
else
{
- if (Type.GetType("Mono.Runtime") != null)
+ if (IsPlatformMono)
ru = "Win/Mono";
else
ru = "Win/.NET";
@@ -1242,79 +1591,56 @@ namespace OpenSim.Framework
return displayConnectionString;
}
- public static T ReadSettingsFromIniFile(IConfig config, T settingsClass)
+ public static string Base64ToString(string str)
{
- Type settingsType = settingsClass.GetType();
+ Decoder utf8Decode = Encoding.UTF8.GetDecoder();
- FieldInfo[] fieldInfos = settingsType.GetFields();
- foreach (FieldInfo fieldInfo in fieldInfos)
+ byte[] todecode_byte = Convert.FromBase64String(str);
+ int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
+ char[] decoded_char = new char[charCount];
+ utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
+ string result = new String(decoded_char);
+ return result;
+ }
+
+ public static void BinaryToASCII(char[] chars)
+ {
+ for (int i = 0; i < chars.Length; i++)
{
- if (!fieldInfo.IsStatic)
- {
- if (fieldInfo.FieldType == typeof(System.String))
- {
- fieldInfo.SetValue(settingsClass, config.Get(fieldInfo.Name, (string)fieldInfo.GetValue(settingsClass)));
- }
- else if (fieldInfo.FieldType == typeof(System.Boolean))
- {
- fieldInfo.SetValue(settingsClass, config.GetBoolean(fieldInfo.Name, (bool)fieldInfo.GetValue(settingsClass)));
- }
- else if (fieldInfo.FieldType == typeof(System.Int32))
- {
- fieldInfo.SetValue(settingsClass, config.GetInt(fieldInfo.Name, (int)fieldInfo.GetValue(settingsClass)));
- }
- else if (fieldInfo.FieldType == typeof(System.Single))
- {
- fieldInfo.SetValue(settingsClass, config.GetFloat(fieldInfo.Name, (float)fieldInfo.GetValue(settingsClass)));
- }
- else if (fieldInfo.FieldType == typeof(System.UInt32))
- {
- fieldInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(fieldInfo.Name, ((uint)fieldInfo.GetValue(settingsClass)).ToString())));
- }
- }
- }
-
- PropertyInfo[] propertyInfos = settingsType.GetProperties();
- foreach (PropertyInfo propInfo in propertyInfos)
- {
- if ((propInfo.CanRead) && (propInfo.CanWrite))
- {
- if (propInfo.PropertyType == typeof(System.String))
- {
- propInfo.SetValue(settingsClass, config.Get(propInfo.Name, (string)propInfo.GetValue(settingsClass, null)), null);
- }
- else if (propInfo.PropertyType == typeof(System.Boolean))
- {
- propInfo.SetValue(settingsClass, config.GetBoolean(propInfo.Name, (bool)propInfo.GetValue(settingsClass, null)), null);
- }
- else if (propInfo.PropertyType == typeof(System.Int32))
- {
- propInfo.SetValue(settingsClass, config.GetInt(propInfo.Name, (int)propInfo.GetValue(settingsClass, null)), null);
- }
- else if (propInfo.PropertyType == typeof(System.Single))
- {
- propInfo.SetValue(settingsClass, config.GetFloat(propInfo.Name, (float)propInfo.GetValue(settingsClass, null)), null);
- }
- if (propInfo.PropertyType == typeof(System.UInt32))
- {
- propInfo.SetValue(settingsClass, Convert.ToUInt32(config.Get(propInfo.Name, ((uint)propInfo.GetValue(settingsClass, null)).ToString())), null);
- }
- }
+ char ch = chars[i];
+ if (ch < 32 || ch > 127)
+ chars[i] = '.';
}
+ }
- return settingsClass;
+ public static string BinaryToASCII(string src)
+ {
+ char[] chars = src.ToCharArray();
+ BinaryToASCII(chars);
+ return new String(chars);
}
- public static string Base64ToString(string str)
+ ///
+ /// Reads a known number of bytes from a stream.
+ /// Throws EndOfStreamException if the stream doesn't contain enough data.
+ ///
+ /// The stream to read data from
+ /// The array to write bytes into. The array
+ /// will be completely filled from the stream, so an appropriate
+ /// size must be given.
+ public static void ReadStream(Stream stream, byte[] data)
{
- Decoder utf8Decode = Encoding.UTF8.GetDecoder();
+ int offset = 0;
+ int remaining = data.Length;
- byte[] todecode_byte = Convert.FromBase64String(str);
- int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
- char[] decoded_char = new char[charCount];
- utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
- string result = new String(decoded_char);
- return result;
+ while (remaining > 0)
+ {
+ int read = stream.Read(data, offset, remaining);
+ if (read <= 0)
+ throw new EndOfStreamException(String.Format("End of stream reached with {0} bytes left to read", remaining));
+ remaining -= read;
+ offset += read;
+ }
}
public static Guid GetHashGuid(string data, string salt)
@@ -1466,32 +1792,6 @@ namespace OpenSim.Framework
return found.ToArray();
}
- public static string ServerURI(string uri)
- {
- if (uri == string.Empty)
- return string.Empty;
-
- // Get rid of eventual slashes at the end
- uri = uri.TrimEnd('/');
-
- IPAddress ipaddr1 = null;
- string port1 = "";
- try
- {
- ipaddr1 = Util.GetHostFromURL(uri);
- }
- catch { }
-
- try
- {
- port1 = uri.Split(new char[] { ':' })[2];
- }
- catch { }
-
- // We tried our best to convert the domain names to IP addresses
- return (ipaddr1 != null) ? "http://" + ipaddr1.ToString() + ":" + port1 : uri;
- }
-
///
/// Convert a string to a byte format suitable for transport in an LLUDP packet. The output is truncated to 256 bytes if necessary.
///
@@ -1518,17 +1818,26 @@ namespace OpenSim.Framework
///
public static byte[] StringToBytes256(string str)
{
- if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; }
- if (str.Length > 254) str = str.Remove(254);
- if (!str.EndsWith("\0")) { str += "\0"; }
+ if (String.IsNullOrEmpty(str))
+ return Utils.EmptyBytes;
+
+ if (!str.EndsWith("\0"))
+ str += "\0";
// Because this is UTF-8 encoding and not ASCII, it's possible we
// might have gotten an oversized array even after the string trim
byte[] data = UTF8.GetBytes(str);
+
if (data.Length > 256)
{
- Array.Resize(ref data, 256);
- data[255] = 0;
+ int cut = 255;
+ if((data[cut] & 0x80 ) != 0 )
+ {
+ while(cut > 0 && (data[cut] & 0xc0) != 0xc0)
+ cut--;
+ }
+ Array.Resize(ref data, cut + 1);
+ data[cut] = 0;
}
return data;
@@ -1560,23 +1869,56 @@ namespace OpenSim.Framework
///
public static byte[] StringToBytes1024(string str)
{
- if (String.IsNullOrEmpty(str)) { return Utils.EmptyBytes; }
- if (str.Length > 1023) str = str.Remove(1023);
- if (!str.EndsWith("\0")) { str += "\0"; }
+ if (String.IsNullOrEmpty(str))
+ return Utils.EmptyBytes;
+
+ if (!str.EndsWith("\0"))
+ str += "\0";
// Because this is UTF-8 encoding and not ASCII, it's possible we
// might have gotten an oversized array even after the string trim
byte[] data = UTF8.GetBytes(str);
+
if (data.Length > 1024)
{
- Array.Resize(ref data, 1024);
- data[1023] = 0;
+ int cut = 1023;
+ if((data[cut] & 0x80 ) != 0 )
+ {
+ while(cut > 0 && (data[cut] & 0xc0) != 0xc0)
+ cut--;
+ }
+ Array.Resize(ref data, cut + 1);
+ data[cut] = 0;
}
return data;
}
///
+ /// Pretty format the hashtable contents to a single line.
+ ///
+ ///
+ /// Used for debugging output.
+ ///
+ ///
+ public static string PrettyFormatToSingleLine(Hashtable ht)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ int i = 0;
+
+ foreach (string key in ht.Keys)
+ {
+ sb.AppendFormat("{0}:{1}", key, ht[key]);
+
+ if (++i < ht.Count)
+ sb.AppendFormat(", ");
+ }
+
+ return sb.ToString();
+ }
+
+ ///
/// Used to trigger an early library load on Windows systems.
///
///
@@ -1646,25 +1988,28 @@ namespace OpenSim.Framework
}
}
- public static void FireAndForget(System.Threading.WaitCallback callback)
- {
- FireAndForget(callback, null);
- }
-
- public static void InitThreadPool(int maxThreads)
+ public static void InitThreadPool(int minThreads, int maxThreads)
{
if (maxThreads < 2)
throw new ArgumentOutOfRangeException("maxThreads", "maxThreads must be greater than 2");
+
+ if (minThreads > maxThreads || minThreads < 2)
+ throw new ArgumentOutOfRangeException("minThreads", "minThreads must be greater than 2 and less than or equal to maxThreads");
+
if (m_ThreadPool != null)
- throw new InvalidOperationException("SmartThreadPool is already initialized");
+ {
+ m_log.Warn("SmartThreadPool is already initialized. Ignoring request.");
+ return;
+ }
STPStartInfo startInfo = new STPStartInfo();
startInfo.ThreadPoolName = "Util";
startInfo.IdleTimeout = 2000;
startInfo.MaxWorkerThreads = maxThreads;
- startInfo.MinWorkerThreads = 2;
+ startInfo.MinWorkerThreads = minThreads;
m_ThreadPool = new SmartThreadPool(startInfo);
+ m_threadPoolWatchdog = new Timer(ThreadPoolWatchdog, null, 0, 1000);
}
public static int FireAndForgetCount()
@@ -1687,15 +2032,179 @@ namespace OpenSim.Framework
throw new NotImplementedException();
}
}
+
+ ///
+ /// Additional information about threads in the main thread pool. Used to time how long the
+ /// thread has been running, and abort it if it has timed-out.
+ ///
+ private class ThreadInfo
+ {
+ public long ThreadFuncNum { get; set; }
+ public string StackTrace { get; set; }
+ private string context;
+ public bool LogThread { get; set; }
+
+ public IWorkItemResult WorkItem { get; set; }
+ public Thread Thread { get; set; }
+ public bool Running { get; set; }
+ public bool Aborted { get; set; }
+ private int started;
+
+ public ThreadInfo(long threadFuncNum, string context)
+ {
+ ThreadFuncNum = threadFuncNum;
+ this.context = context;
+ LogThread = false;
+ Thread = null;
+ Running = false;
+ Aborted = false;
+ }
+
+ public void Started()
+ {
+ Thread = Thread.CurrentThread;
+ started = EnvironmentTickCount();
+ Running = true;
+ }
+
+ public void Ended()
+ {
+ Running = false;
+ }
+
+ public int Elapsed()
+ {
+ return EnvironmentTickCountSubtract(started);
+ }
+
+ public void Abort()
+ {
+ Aborted = true;
+ WorkItem.Cancel(true);
+ }
+
+ ///
+ /// Returns the thread's stack trace.
+ ///
+ ///
+ /// May return one of two stack traces. First, tries to get the thread's active stack
+ /// trace. But this can fail, so as a fallback this method will return the stack
+ /// trace that was active when the task was queued.
+ ///
+ public string GetStackTrace()
+ {
+ string ret = (context == null) ? "" : ("(" + context + ") ");
+
+ StackTrace activeStackTrace = Util.GetStackTrace(Thread);
+ if (activeStackTrace != null)
+ ret += activeStackTrace.ToString();
+ else if (StackTrace != null)
+ ret += "(Stack trace when queued) " + StackTrace;
+ // else, no stack trace available
+
+ return ret;
+ }
+ }
+
+ private static long nextThreadFuncNum = 0;
+ private static long numQueuedThreadFuncs = 0;
+ private static long numRunningThreadFuncs = 0;
+ private static long numTotalThreadFuncsCalled = 0;
+ private static Int32 threadFuncOverloadMode = 0;
+
+ public static long TotalQueuedFireAndForgetCalls { get { return numQueuedThreadFuncs; } }
+ public static long TotalRunningFireAndForgetCalls { get { return numRunningThreadFuncs; } }
+
+ // Maps (ThreadFunc number -> Thread)
+ private static ConcurrentDictionary activeThreads = new ConcurrentDictionary();
+
+ private static readonly int THREAD_TIMEOUT = 10 * 60 * 1000; // 10 minutes
+
+ ///
+ /// Finds threads in the main thread pool that have timed-out, and aborts them.
+ ///
+ private static void ThreadPoolWatchdog(object state)
+ {
+ foreach (KeyValuePair entry in activeThreads)
+ {
+ ThreadInfo t = entry.Value;
+ if (t.Running && !t.Aborted && (t.Elapsed() >= THREAD_TIMEOUT))
+ {
+ m_log.WarnFormat("Timeout in threadfunc {0} ({1}) {2}", t.ThreadFuncNum, t.Thread.Name, t.GetStackTrace());
+ t.Abort();
+
+ ThreadInfo dummy;
+ activeThreads.TryRemove(entry.Key, out dummy);
+
+ // It's possible that the thread won't abort. To make sure the thread pool isn't
+ // depleted, increase the pool size.
+ m_ThreadPool.MaxThreads++;
+ }
+ }
+ }
+
+ public static long TotalFireAndForgetCallsMade { get { return numTotalThreadFuncsCalled; } }
+
+ public static Dictionary GetFireAndForgetCallsMade()
+ {
+ return new Dictionary(m_fireAndForgetCallsMade);
+ }
+
+ private static Dictionary m_fireAndForgetCallsMade = new Dictionary();
+
+ public static Dictionary GetFireAndForgetCallsInProgress()
+ {
+ return new Dictionary(m_fireAndForgetCallsInProgress);
+ }
+
+ private static Dictionary m_fireAndForgetCallsInProgress = new Dictionary();
+
+ public static void FireAndForget(System.Threading.WaitCallback callback)
+ {
+ FireAndForget(callback, null, null);
+ }
public static void FireAndForget(System.Threading.WaitCallback callback, object obj)
{
+ FireAndForget(callback, obj, null);
+ }
+
+ public static void FireAndForget(System.Threading.WaitCallback callback, object obj, string context)
+ {
+ Interlocked.Increment(ref numTotalThreadFuncsCalled);
+
+ if (context != null)
+ {
+ if (!m_fireAndForgetCallsMade.ContainsKey(context))
+ m_fireAndForgetCallsMade[context] = 1;
+ else
+ m_fireAndForgetCallsMade[context]++;
+
+ if (!m_fireAndForgetCallsInProgress.ContainsKey(context))
+ m_fireAndForgetCallsInProgress[context] = 1;
+ else
+ m_fireAndForgetCallsInProgress[context]++;
+ }
+
WaitCallback realCallback;
+ bool loggingEnabled = LogThreadPool > 0;
+
+ long threadFuncNum = Interlocked.Increment(ref nextThreadFuncNum);
+ ThreadInfo threadInfo = new ThreadInfo(threadFuncNum, context);
+
if (FireAndForgetMethod == FireAndForgetMethod.RegressionTest)
{
// If we're running regression tests, then we want any exceptions to rise up to the test code.
- realCallback = o => { Culture.SetCurrentCulture(); callback(o); };
+ realCallback =
+ o =>
+ {
+ Culture.SetCurrentCulture();
+ callback(o);
+
+ if (context != null)
+ m_fireAndForgetCallsInProgress[context]--;
+ };
}
else
{
@@ -1704,118 +2213,291 @@ namespace OpenSim.Framework
// for decimals places but is read by a culture that treats commas as number seperators.
realCallback = o =>
{
- Culture.SetCurrentCulture();
+ long numQueued1 = Interlocked.Decrement(ref numQueuedThreadFuncs);
+ long numRunning1 = Interlocked.Increment(ref numRunningThreadFuncs);
+ threadInfo.Started();
+ activeThreads[threadFuncNum] = threadInfo;
try
{
+ if ((loggingEnabled || (threadFuncOverloadMode == 1)) && threadInfo.LogThread)
+ m_log.DebugFormat("Run threadfunc {0} (Queued {1}, Running {2})", threadFuncNum, numQueued1, numRunning1);
+
+ Culture.SetCurrentCulture();
+
callback(o);
}
+ catch (ThreadAbortException e)
+ {
+ m_log.Error(string.Format("Aborted threadfunc {0} ", threadFuncNum), e);
+ }
catch (Exception e)
{
- m_log.ErrorFormat(
- "[UTIL]: Continuing after async_call_method thread terminated with exception {0}{1}",
- e.Message, e.StackTrace);
+ m_log.Error(string.Format("[UTIL]: Util STP threadfunc {0} terminated with error ", threadFuncNum), e);
+ }
+ finally
+ {
+ Interlocked.Decrement(ref numRunningThreadFuncs);
+ threadInfo.Ended();
+ ThreadInfo dummy;
+ activeThreads.TryRemove(threadFuncNum, out dummy);
+ if ((loggingEnabled || (threadFuncOverloadMode == 1)) && threadInfo.LogThread)
+ m_log.DebugFormat("Exit threadfunc {0} ({1})", threadFuncNum, FormatDuration(threadInfo.Elapsed()));
+
+ if (context != null)
+ m_fireAndForgetCallsInProgress[context]--;
}
};
}
- switch (FireAndForgetMethod)
+ long numQueued = Interlocked.Increment(ref numQueuedThreadFuncs);
+ try
{
- case FireAndForgetMethod.RegressionTest:
- case FireAndForgetMethod.None:
- realCallback.Invoke(obj);
- break;
- case FireAndForgetMethod.UnsafeQueueUserWorkItem:
- ThreadPool.UnsafeQueueUserWorkItem(realCallback, obj);
- break;
- case FireAndForgetMethod.QueueUserWorkItem:
- ThreadPool.QueueUserWorkItem(realCallback, obj);
- break;
- case FireAndForgetMethod.BeginInvoke:
- FireAndForgetWrapper wrapper = FireAndForgetWrapper.Instance;
- wrapper.FireAndForget(realCallback, obj);
- break;
- case FireAndForgetMethod.SmartThreadPool:
- if (m_ThreadPool == null)
- InitThreadPool(15);
- m_ThreadPool.QueueWorkItem(SmartThreadPoolCallback, new object[] { realCallback, obj });
- break;
- case FireAndForgetMethod.Thread:
- Thread thread = new Thread(delegate(object o) { realCallback(o); });
- thread.Start(obj);
- break;
- default:
- throw new NotImplementedException();
+ long numRunning = numRunningThreadFuncs;
+
+ if (m_ThreadPool != null && LogOverloads)
+ {
+ if ((threadFuncOverloadMode == 0) && (numRunning >= m_ThreadPool.MaxThreads))
+ {
+ if (Interlocked.CompareExchange(ref threadFuncOverloadMode, 1, 0) == 0)
+ m_log.DebugFormat("Threadfunc: enable overload mode (Queued {0}, Running {1})", numQueued, numRunning);
+ }
+ else if ((threadFuncOverloadMode == 1) && (numRunning <= (m_ThreadPool.MaxThreads * 2) / 3))
+ {
+ if (Interlocked.CompareExchange(ref threadFuncOverloadMode, 0, 1) == 1)
+ m_log.DebugFormat("Threadfunc: disable overload mode (Queued {0}, Running {1})", numQueued, numRunning);
+ }
+ }
+
+ if (loggingEnabled || (threadFuncOverloadMode == 1))
+ {
+ string full, partial;
+ GetFireAndForgetStackTrace(out full, out partial);
+ threadInfo.StackTrace = full;
+ threadInfo.LogThread = ShouldLogThread(partial);
+
+ if (threadInfo.LogThread)
+ {
+ m_log.DebugFormat("Queue threadfunc {0} (Queued {1}, Running {2}) {3}{4}",
+ threadFuncNum, numQueued, numRunningThreadFuncs,
+ (context == null) ? "" : ("(" + context + ") "),
+ (LogThreadPool >= 2) ? full : partial);
+ }
+ }
+ else
+ {
+ // Since we didn't log "Queue threadfunc", don't log "Run threadfunc" or "End threadfunc" either.
+ // Those log lines aren't useful when we don't know which function is running in the thread.
+ threadInfo.LogThread = false;
+ }
+
+ switch (FireAndForgetMethod)
+ {
+ case FireAndForgetMethod.RegressionTest:
+ case FireAndForgetMethod.None:
+ realCallback.Invoke(obj);
+ break;
+ case FireAndForgetMethod.UnsafeQueueUserWorkItem:
+ ThreadPool.UnsafeQueueUserWorkItem(realCallback, obj);
+ break;
+ case FireAndForgetMethod.QueueUserWorkItem:
+ ThreadPool.QueueUserWorkItem(realCallback, obj);
+ break;
+ case FireAndForgetMethod.BeginInvoke:
+ FireAndForgetWrapper wrapper = FireAndForgetWrapper.Instance;
+ wrapper.FireAndForget(realCallback, obj);
+ break;
+ case FireAndForgetMethod.SmartThreadPool:
+ if (m_ThreadPool == null)
+ InitThreadPool(2, 15);
+ threadInfo.WorkItem = m_ThreadPool.QueueWorkItem((cb, o) => cb(o), realCallback, obj);
+ break;
+ case FireAndForgetMethod.Thread:
+ Thread thread = new Thread(delegate(object o) { realCallback(o); });
+ thread.Start(obj);
+ break;
+ default:
+ throw new NotImplementedException();
+ }
+ }
+ catch (Exception)
+ {
+ Interlocked.Decrement(ref numQueuedThreadFuncs);
+ ThreadInfo dummy;
+ activeThreads.TryRemove(threadFuncNum, out dummy);
+ throw;
}
}
///
- /// Get a thread pool report.
+ /// Returns whether the thread should be logged. Some very common threads aren't logged,
+ /// to avoid filling up the log.
///
- ///
- public static string GetThreadPoolReport()
+ /// A partial stack trace of where the thread was queued
+ /// Whether to log this thread
+ private static bool ShouldLogThread(string stackTrace)
{
- string threadPoolUsed = null;
- int maxThreads = 0;
- int minThreads = 0;
- int allocatedThreads = 0;
- int inUseThreads = 0;
- int waitingCallbacks = 0;
- int completionPortThreads = 0;
+ if (LogThreadPool < 3)
+ {
+ if (stackTrace.Contains("BeginFireQueueEmpty"))
+ return false;
+ }
+
+ return true;
+ }
- StringBuilder sb = new StringBuilder();
- if (FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool)
+ ///
+ /// Returns a stack trace for a thread added using FireAndForget().
+ ///
+ /// Will contain the full stack trace
+ /// Will contain only the first frame of the stack trace
+ private static void GetFireAndForgetStackTrace(out string full, out string partial)
+ {
+ string src = Environment.StackTrace;
+ string[] lines = src.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
+
+ StringBuilder dest = new StringBuilder(src.Length);
+
+ bool started = false;
+ bool first = true;
+ partial = "";
+
+ for (int i = 0; i < lines.Length; i++)
{
- // ROBUST currently leaves this the FireAndForgetMethod but never actually initializes the threadpool.
- if (m_ThreadPool != null)
+ string line = lines[i];
+
+ if (!started)
{
- threadPoolUsed = "SmartThreadPool";
- maxThreads = m_ThreadPool.MaxThreads;
- minThreads = m_ThreadPool.MinThreads;
- inUseThreads = m_ThreadPool.InUseThreads;
- allocatedThreads = m_ThreadPool.ActiveThreads;
- waitingCallbacks = m_ThreadPool.WaitingCallbacks;
+ // Skip the initial stack frames, because they're of no interest for debugging
+ if (line.Contains("StackTrace") || line.Contains("FireAndForget"))
+ continue;
+ started = true;
}
+
+ if (first)
+ {
+ line = line.TrimStart();
+ first = false;
+ partial = line;
+ }
+
+ bool last = (i == lines.Length - 1);
+ if (last)
+ dest.Append(line);
+ else
+ dest.AppendLine(line);
}
- else if (
- FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem
- || FireAndForgetMethod == FireAndForgetMethod.UnsafeQueueUserWorkItem)
+
+ full = dest.ToString();
+ }
+
+#pragma warning disable 0618
+ ///
+ /// Return the stack trace of a different thread.
+ ///
+ ///
+ /// This is complicated because the thread needs to be paused in order to get its stack
+ /// trace. And pausing another thread can cause a deadlock. This method attempts to
+ /// avoid deadlock by using a short timeout (200ms), after which it gives up and
+ /// returns 'null' instead of the stack trace.
+ ///
+ /// Take from: http://stackoverflow.com/a/14935378
+ ///
+ /// WARNING: this doesn't work in Mono. See https://bugzilla.novell.com/show_bug.cgi?id=571691
+ ///
+ ///
+ /// The stack trace, or null if failed to get it
+ private static StackTrace GetStackTrace(Thread targetThread)
+ {
+ if (IsPlatformMono)
{
- threadPoolUsed = "BuiltInThreadPool";
- ThreadPool.GetMaxThreads(out maxThreads, out completionPortThreads);
- ThreadPool.GetMinThreads(out minThreads, out completionPortThreads);
- int availableThreads;
- ThreadPool.GetAvailableThreads(out availableThreads, out completionPortThreads);
- inUseThreads = maxThreads - availableThreads;
- allocatedThreads = -1;
- waitingCallbacks = -1;
+ // This doesn't work in Mono
+ return null;
}
- if (threadPoolUsed != null)
+ ManualResetEventSlim fallbackThreadReady = new ManualResetEventSlim();
+ ManualResetEventSlim exitedSafely = new ManualResetEventSlim();
+
+ try
{
- sb.AppendFormat("Thread pool used : {0}\n", threadPoolUsed);
- sb.AppendFormat("Max threads : {0}\n", maxThreads);
- sb.AppendFormat("Min threads : {0}\n", minThreads);
- sb.AppendFormat("Allocated threads : {0}\n", allocatedThreads < 0 ? "not applicable" : allocatedThreads.ToString());
- sb.AppendFormat("In use threads : {0}\n", inUseThreads);
- sb.AppendFormat("Work items waiting : {0}\n", waitingCallbacks < 0 ? "not available" : waitingCallbacks.ToString());
+ new Thread(delegate()
+ {
+ fallbackThreadReady.Set();
+ while (!exitedSafely.Wait(200))
+ {
+ try
+ {
+ targetThread.Resume();
+ }
+ catch (Exception)
+ {
+ // Whatever happens, do never stop to resume the main-thread regularly until the main-thread has exited safely.
+ }
+ }
+ }).Start();
+
+ fallbackThreadReady.Wait();
+ // From here, you have about 200ms to get the stack-trace
+
+ targetThread.Suspend();
+
+ StackTrace trace = null;
+ try
+ {
+ trace = new StackTrace(targetThread, true);
+ }
+ catch (ThreadStateException)
+ {
+ //failed to get stack trace, since the fallback-thread resumed the thread
+ //possible reasons:
+ //1.) This thread was just too slow
+ //2.) A deadlock ocurred
+ //Automatic retry seems too risky here, so just return null.
+ }
+
+ try
+ {
+ targetThread.Resume();
+ }
+ catch (ThreadStateException)
+ {
+ // Thread is running again already
+ }
+
+ return trace;
}
- else
+ finally
{
- sb.AppendFormat("Thread pool not used\n");
+ // Signal the fallack-thread to stop
+ exitedSafely.Set();
}
-
- return sb.ToString();
}
+#pragma warning restore 0618
- private static object SmartThreadPoolCallback(object o)
+ ///
+ /// Get information about the current state of the smart thread pool.
+ ///
+ ///
+ /// null if this isn't the pool being used for non-scriptengine threads.
+ ///
+ public static STPInfo GetSmartThreadPoolInfo()
{
- object[] array = (object[])o;
- WaitCallback callback = (WaitCallback)array[0];
- object obj = array[1];
+ if (m_ThreadPool == null)
+ return null;
- callback(obj);
- return null;
+ STPInfo stpi = new STPInfo();
+ stpi.Name = m_ThreadPool.Name;
+ stpi.STPStartInfo = m_ThreadPool.STPStartInfo;
+ stpi.IsIdle = m_ThreadPool.IsIdle;
+ stpi.IsShuttingDown = m_ThreadPool.IsShuttingdown;
+ stpi.MaxThreads = m_ThreadPool.MaxThreads;
+ stpi.MinThreads = m_ThreadPool.MinThreads;
+ stpi.InUseThreads = m_ThreadPool.InUseThreads;
+ stpi.ActiveThreads = m_ThreadPool.ActiveThreads;
+ stpi.WaitingCallbacks = m_ThreadPool.WaitingCallbacks;
+ stpi.MaxConcurrentWorkItems = m_ThreadPool.Concurrency;
+
+ return stpi;
}
#endregion FireAndForget Threading Pattern
@@ -1876,6 +2558,60 @@ namespace OpenSim.Framework
}
///
+ /// Formats a duration (given in milliseconds).
+ ///
+ public static string FormatDuration(int ms)
+ {
+ TimeSpan span = new TimeSpan(ms * TimeSpan.TicksPerMillisecond);
+
+ string str = "";
+ string suffix = null;
+
+ int hours = (int)span.TotalHours;
+ if (hours > 0)
+ {
+ str += hours.ToString(str.Length == 0 ? "0" : "00");
+ suffix = "hours";
+ }
+
+ if ((hours > 0) || (span.Minutes > 0))
+ {
+ if (str.Length > 0)
+ str += ":";
+ str += span.Minutes.ToString(str.Length == 0 ? "0" : "00");
+ if (suffix == null)
+ suffix = "min";
+ }
+
+ if ((hours > 0) || (span.Minutes > 0) || (span.Seconds > 0))
+ {
+ if (str.Length > 0)
+ str += ":";
+ str += span.Seconds.ToString(str.Length == 0 ? "0" : "00");
+ if (suffix == null)
+ suffix = "sec";
+ }
+
+ if (suffix == null)
+ suffix = "ms";
+
+ if (span.TotalMinutes < 1)
+ {
+ int ms1 = span.Milliseconds;
+ if (str.Length > 0)
+ {
+ ms1 /= 100;
+ str += ".";
+ }
+ str += ms1.ToString("0");
+ }
+
+ str += " " + suffix;
+
+ return str;
+ }
+
+ ///
/// Prints the call stack at any given point. Useful for debugging.
///
public static void PrintCallStack()
@@ -1942,16 +2678,18 @@ namespace OpenSim.Framework
}
#region Xml Serialization Utilities
- public static bool ReadBoolean(XmlTextReader reader)
+ public static bool ReadBoolean(XmlReader reader)
{
+ // AuroraSim uses "int" for some fields that are boolean in OpenSim, e.g. "PassCollisions". Don't fail because of this.
reader.ReadStartElement();
- bool result = Boolean.Parse(reader.ReadContentAsString().ToLower());
+ string val = reader.ReadContentAsString().ToLower();
+ bool result = val.Equals("true") || val.Equals("1");
reader.ReadEndElement();
return result;
}
- public static UUID ReadUUID(XmlTextReader reader, string name)
+ public static UUID ReadUUID(XmlReader reader, string name)
{
UUID id;
string idStr;
@@ -1970,7 +2708,7 @@ namespace OpenSim.Framework
return id;
}
- public static Vector3 ReadVector(XmlTextReader reader, string name)
+ public static Vector3 ReadVector(XmlReader reader, string name)
{
Vector3 vec;
@@ -1983,7 +2721,7 @@ namespace OpenSim.Framework
return vec;
}
- public static Quaternion ReadQuaternion(XmlTextReader reader, string name)
+ public static Quaternion ReadQuaternion(XmlReader reader, string name)
{
Quaternion quat = new Quaternion();
@@ -2012,7 +2750,7 @@ namespace OpenSim.Framework
return quat;
}
- public static T ReadEnum(XmlTextReader reader, string name)
+ public static T ReadEnum(XmlReader reader, string name)
{
string value = reader.ReadElementContentAsString(name, String.Empty);
// !!!!! to deal with flags without commas
@@ -2024,7 +2762,9 @@ namespace OpenSim.Framework
#endregion
#region Universal User Identifiers
- ///
+
+ ///
+ /// Attempts to parse a UUI into its components: UUID, name and URL.
///
/// uuid[;endpoint[;first last[;secret]]]
/// the uuid part
@@ -2034,7 +2774,7 @@ namespace OpenSim.Framework
/// the secret part
public static bool ParseUniversalUserIdentifier(string value, out UUID uuid, out string url, out string firstname, out string lastname, out string secret)
{
- uuid = UUID.Zero; url = string.Empty; firstname = "Unknown"; lastname = "User"; secret = string.Empty;
+ uuid = UUID.Zero; url = string.Empty; firstname = "Unknown"; lastname = "UserUPUUI"; secret = string.Empty;
string[] parts = value.Split(';');
if (parts.Length >= 1)
@@ -2060,6 +2800,27 @@ namespace OpenSim.Framework
}
///
+ /// For foreign avatars, extracts their original name and Server URL from their First Name and Last Name.
+ ///
+ public static bool ParseForeignAvatarName(string firstname, string lastname,
+ out string realFirstName, out string realLastName, out string serverURI)
+ {
+ realFirstName = realLastName = serverURI = string.Empty;
+
+ if (!lastname.Contains("@"))
+ return false;
+
+ if (!firstname.Contains("."))
+ return false;
+
+ realFirstName = firstname.Split('.')[0];
+ realLastName = firstname.Split('.')[1];
+ serverURI = new Uri("http://" + lastname.Replace("@", "")).ToString();
+
+ return true;
+ }
+
+ ///
/// Produces a universal (HG) system-facing identifier given the information
///
///
@@ -2092,10 +2853,15 @@ namespace OpenSim.Framework
{
string[] parts = firstName.Split(new char[] { '.' });
if (parts.Length == 2)
- return id.ToString() + ";" + agentsURI + ";" + parts[0] + " " + parts[1];
+ return CalcUniversalIdentifier(id, agentsURI, parts[0] + " " + parts[1]);
}
- return id.ToString() + ";" + agentsURI + ";" + firstName + " " + lastName;
+
+ return CalcUniversalIdentifier(id, agentsURI, firstName + " " + lastName);
+ }
+ private static string CalcUniversalIdentifier(UUID id, string agentsURI, string name)
+ {
+ return id.ToString() + ";" + agentsURI + ";" + name;
}
///
@@ -2119,5 +2885,221 @@ namespace OpenSim.Framework
return firstName + "." + lastName + " " + "@" + uri.Authority;
}
#endregion
+
+ ///
+ /// Escapes the special characters used in "LIKE".
+ ///
+ ///
+ /// For example: EscapeForLike("foo_bar%baz") = "foo\_bar\%baz"
+ ///
+ public static string EscapeForLike(string str)
+ {
+ return str.Replace("_", "\\_").Replace("%", "\\%");
+ }
+
+ ///
+ /// Returns the name of the user's viewer.
+ ///
+ ///
+ /// This method handles two ways that viewers specify their name:
+ /// 1. Viewer = "Firestorm-Release 4.4.2.34167", Channel = "(don't care)" -> "Firestorm-Release 4.4.2.34167"
+ /// 2. Viewer = "4.5.1.38838", Channel = "Firestorm-Beta" -> "Firestorm-Beta 4.5.1.38838"
+ ///
+ public static string GetViewerName(AgentCircuitData agent)
+ {
+ string name = agent.Viewer;
+ if (name == null)
+ name = "";
+ else
+ name = name.Trim();
+
+ // Check if 'Viewer' is just a version number. If it's *not*, then we
+ // assume that it contains the real viewer name, and we return it.
+ foreach (char c in name)
+ {
+ if (Char.IsLetter(c))
+ return name;
+ }
+
+ // The 'Viewer' string contains just a version number. If there's anything in
+ // 'Channel' then assume that it's the viewer name.
+ if ((agent.Channel != null) && (agent.Channel.Length > 0))
+ name = agent.Channel.Trim() + " " + name;
+
+ return name;
+ }
+
+ public static void LogFailedXML(string message, string xml)
+ {
+ int length = xml.Length;
+ if (length > 2000)
+ xml = xml.Substring(0, 2000) + "...";
+
+ m_log.ErrorFormat("{0} Failed XML ({1} bytes) = {2}", message, length, xml);
+ }
+
+ }
+
+ public class DoubleQueue where T:class
+ {
+ private Queue m_lowQueue = new Queue();
+ private Queue m_highQueue = new Queue();
+
+ private object m_syncRoot = new object();
+ private Semaphore m_s = new Semaphore(0, 1);
+
+ public DoubleQueue()
+ {
+ }
+
+ public virtual int Count
+ {
+ get
+ {
+ lock (m_syncRoot)
+ return m_highQueue.Count + m_lowQueue.Count;
+ }
+ }
+
+ public virtual void Enqueue(T data)
+ {
+ Enqueue(m_lowQueue, data);
+ }
+
+ public virtual void EnqueueLow(T data)
+ {
+ Enqueue(m_lowQueue, data);
+ }
+
+ public virtual void EnqueueHigh(T data)
+ {
+ Enqueue(m_highQueue, data);
+ }
+
+ private void Enqueue(Queue q, T data)
+ {
+ lock (m_syncRoot)
+ {
+ q.Enqueue(data);
+ m_s.WaitOne(0);
+ m_s.Release();
+ }
+ }
+
+ public virtual T Dequeue()
+ {
+ return Dequeue(Timeout.Infinite);
+ }
+
+ public virtual T Dequeue(int tmo)
+ {
+ return Dequeue(TimeSpan.FromMilliseconds(tmo));
+ }
+
+ public virtual T Dequeue(TimeSpan wait)
+ {
+ T res = null;
+
+ if (!Dequeue(wait, ref res))
+ return null;
+
+ return res;
+ }
+
+ public bool Dequeue(int timeout, ref T res)
+ {
+ return Dequeue(TimeSpan.FromMilliseconds(timeout), ref res);
+ }
+
+ public bool Dequeue(TimeSpan wait, ref T res)
+ {
+ if (!m_s.WaitOne(wait))
+ return false;
+
+ lock (m_syncRoot)
+ {
+ if (m_highQueue.Count > 0)
+ res = m_highQueue.Dequeue();
+ else if (m_lowQueue.Count > 0)
+ res = m_lowQueue.Dequeue();
+
+ if (m_highQueue.Count == 0 && m_lowQueue.Count == 0)
+ return true;
+
+ try
+ {
+ m_s.Release();
+ }
+ catch
+ {
+ }
+
+ return true;
+ }
+ }
+
+ public virtual void Clear()
+ {
+
+ lock (m_syncRoot)
+ {
+ // Make sure sem count is 0
+ m_s.WaitOne(0);
+
+ m_lowQueue.Clear();
+ m_highQueue.Clear();
+ }
+ }
+ }
+
+ public class BetterRandom
+ {
+ private const int BufferSize = 1024; // must be a multiple of 4
+ private byte[] RandomBuffer;
+ private int BufferOffset;
+ private RNGCryptoServiceProvider rng;
+ public BetterRandom()
+ {
+ RandomBuffer = new byte[BufferSize];
+ rng = new RNGCryptoServiceProvider();
+ BufferOffset = RandomBuffer.Length;
+ }
+ private void FillBuffer()
+ {
+ rng.GetBytes(RandomBuffer);
+ BufferOffset = 0;
+ }
+ public int Next()
+ {
+ if (BufferOffset >= RandomBuffer.Length)
+ {
+ FillBuffer();
+ }
+ int val = BitConverter.ToInt32(RandomBuffer, BufferOffset) & 0x7fffffff;
+ BufferOffset += sizeof(int);
+ return val;
+ }
+ public int Next(int maxValue)
+ {
+ return Next() % maxValue;
+ }
+ public int Next(int minValue, int maxValue)
+ {
+ if (maxValue < minValue)
+ {
+ throw new ArgumentOutOfRangeException("maxValue must be greater than or equal to minValue");
+ }
+ int range = maxValue - minValue;
+ return minValue + Next(range);
+ }
+ public double NextDouble()
+ {
+ int val = Next();
+ return (double)val / int.MaxValue;
+ }
+ public void GetBytes(byte[] buff)
+ {
+ rng.GetBytes(buff);
+ }
}
}
--
cgit v1.1