From 55b2d010281f989beeb0088bde95245bb1f99054 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Sat, 30 Jul 2016 01:44:33 +0100 Subject: [PATCH] in RegionInfoCache, replace lib omv ExpireCaches by a single dedicated one --- .../Grid/RegionInfoCache.cs | 510 +++++++++++++++--- 1 file changed, 443 insertions(+), 67 deletions(-) diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs index be8a9a2307..4dc9033283 100644 --- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs +++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs @@ -26,6 +26,7 @@ */ using System; using System.Reflection; +using System.Threading; using System.Collections.Generic; using OpenSim.Framework; using OpenSim.Services.Interfaces; @@ -37,54 +38,17 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid { public class RegionInfoCache { - private const double CACHE_EXPIRATION_SECONDS = 300.0; // 5 minutes + private const double CACHE_EXPIRATION_SECONDS = 120; // 2 minutes opensim regions change a lot // private static readonly ILog m_log = // LogManager.GetLogger( // MethodBase.GetCurrentMethod().DeclaringType); - internal struct ScopedRegionUUID - { - public UUID m_scopeID; - public UUID m_regionID; - public ScopedRegionUUID(UUID scopeID, UUID regionID) - { - m_scopeID = scopeID; - m_regionID = regionID; - } - } - - internal struct ScopedRegionName - { - public UUID m_scopeID; - public string m_name; - public ScopedRegionName(UUID scopeID, string name) - { - m_scopeID = scopeID; - m_name = name; - } - } - - internal struct ScopedRegionPosition - { - public UUID m_scopeID; - public ulong m_regionHandle; - public ScopedRegionPosition(UUID scopeID, ulong handle) - { - m_scopeID = scopeID; - m_regionHandle = handle; - } - } - - private ExpiringCache m_UUIDCache; - private ExpiringCache m_NameCache; - private ExpiringCache m_PositionCache; + private RegionsExpiringCache m_Cache; public RegionInfoCache() { - m_UUIDCache = new ExpiringCache(); - m_NameCache = new ExpiringCache(); - m_PositionCache = new ExpiringCache(); + m_Cache = new RegionsExpiringCache(); } public void Cache(GridRegion rinfo) @@ -101,18 +65,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid if (rinfo == null) return; - ScopedRegionUUID id = new ScopedRegionUUID(scopeID,regionID); - - // Cache even null accounts - m_UUIDCache.AddOrUpdate(id, rinfo, CACHE_EXPIRATION_SECONDS); - if (rinfo != null) - { - ScopedRegionName name = new ScopedRegionName(scopeID,rinfo.RegionName); - m_NameCache.AddOrUpdate(name, id, CACHE_EXPIRATION_SECONDS); - - ScopedRegionPosition pos = new ScopedRegionPosition(scopeID, rinfo.RegionHandle); - m_PositionCache.AddOrUpdate(pos, rinfo, CACHE_EXPIRATION_SECONDS); - } + m_Cache.AddOrUpdate(scopeID, rinfo, CACHE_EXPIRATION_SECONDS); } public GridRegion Get(UUID scopeID, UUID regionID, out bool inCache) @@ -120,8 +73,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid inCache = false; GridRegion rinfo = null; - ScopedRegionUUID id = new ScopedRegionUUID(scopeID,regionID); - if (m_UUIDCache.TryGetValue(id, out rinfo)) + if (m_Cache.TryGetValue(scopeID, regionID, out rinfo)) { inCache = true; return rinfo; @@ -135,8 +87,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid inCache = false; GridRegion rinfo = null; - ScopedRegionPosition pos = new ScopedRegionPosition(scopeID, handle); - if (m_PositionCache.TryGetValue(pos, out rinfo)) + if (m_Cache.TryGetValue(scopeID, handle, out rinfo)) { inCache = true; return rinfo; @@ -145,25 +96,450 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid return null; } - public GridRegion Get(UUID scopeID, string name, out bool inCache) { inCache = false; - ScopedRegionName sname = new ScopedRegionName(scopeID,name); - - ScopedRegionUUID id; - if (m_NameCache.TryGetValue(sname, out id)) + GridRegion rinfo = null; + if (m_Cache.TryGetValue(scopeID, name, out rinfo)) { - GridRegion rinfo = null; - if (m_UUIDCache.TryGetValue(id, out rinfo)) - { - inCache = true; - return rinfo; - } + inCache = true; + return rinfo; } return null; } } + + + // following code partialy adapted from lib OpenMetaverse + public class RegionKey : IComparable + { + private UUID m_scopeID; + private UUID m_RegionID; + private DateTime m_expirationDate; + + public RegionKey(UUID scopeID, UUID id) + { + m_scopeID = scopeID; + m_RegionID = id; + } + + public UUID ScopeID + { + get { return m_scopeID; } + } + public DateTime ExpirationDate + { + get { return m_expirationDate; } + set { m_expirationDate = value; } + } + + public int GetHaskCode() + { + int hash = m_scopeID.GetHashCode(); + hash += hash * 23 + m_RegionID.GetHashCode(); + return hash; + } + + public int CompareTo(RegionKey other) + { + return GetHashCode().CompareTo(other.GetHashCode()); + } + } + + public class RegionInfoByScope + { + private Dictionary byname; + private Dictionary byhandle; + + public RegionInfoByScope() + { + byname = new Dictionary(); + byhandle = new Dictionary(); + } + + public RegionInfoByScope(GridRegion region, RegionKey key) + { + byname = new Dictionary(); + byhandle = new Dictionary(); + + byname[region.RegionName] = key; + byhandle[region.RegionHandle] = key; + } + + public void AddRegion(GridRegion region, RegionKey key) + { + if(byname == null) + byname = new Dictionary(); + if(byhandle == null) + byhandle = new Dictionary(); + + byname[region.RegionName] = key; + byhandle[region.RegionHandle] = key; + } + + public void RemoveRegion(GridRegion region) + { + if(byname != null) + byname.Remove(region.RegionName); + if(byhandle != null) + byhandle.Remove(region.RegionHandle); + } + + public void Clear() + { + if(byname != null) + byname.Clear(); + if(byhandle != null) + byhandle.Clear(); + byname = null; + byhandle = null; + } + + public RegionKey get(string name) + { + if(byname == null || !byname.ContainsKey(name)) + return null; + return byname[name]; + } + + public RegionKey get(ulong handle) + { + if(byhandle == null || !byhandle.ContainsKey(handle)) + return null; + return byhandle[handle]; + } + + public int Count() + { + if(byname == null) + return 0; + else + return byname.Count; + } + } + + public sealed class RegionsExpiringCache + { + const double CACHE_PURGE_HZ = 60; + const int MAX_LOCK_WAIT = 5000; // milliseconds + + /// For thread safety + object syncRoot = new object(); + /// For thread safety + object isPurging = new object(); + + Dictionary timedStorage = new Dictionary(); + Dictionary InfobyScope = new Dictionary(); + private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds); + + public RegionsExpiringCache() + { + timer.Elapsed += PurgeCache; + timer.Start(); + } + + public bool Add(UUID scope, GridRegion region, double expirationSeconds) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + + RegionKey key = new RegionKey(scope , region.RegionID); + + try + { + if (timedStorage.ContainsKey(key)) + return false; + + key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds); + timedStorage.Add(key, region); + + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + { + ris = new RegionInfoByScope(region, key); + InfobyScope[scope] = ris; + } + else + ris.AddRegion(region, key); + + return true; + } + finally { Monitor.Exit(syncRoot);} + } + + public bool AddOrUpdate(UUID scope, GridRegion region, double expirationSeconds) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + + try + { + RegionKey key = new RegionKey(scope, region.RegionID); + key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds); + + if (timedStorage.ContainsKey(key)) + { + timedStorage.Remove(key); + timedStorage.Add(key, region); + + if(!InfobyScope.ContainsKey(scope)) + { + RegionInfoByScope ris = new RegionInfoByScope(region, key); + InfobyScope[scope] = ris; + } + return false; + } + else + { + timedStorage.Add(key, region); + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + { + ris = new RegionInfoByScope(region,key); + InfobyScope[scope] = ris; + } + else + ris.AddRegion(region,key); + return true; + } + } + finally { Monitor.Exit(syncRoot); } + } + + public void Clear() + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + timedStorage.Clear(); + InfobyScope.Clear(); + } + finally { Monitor.Exit(syncRoot); } + } + + public bool Contains(UUID scope, GridRegion region) + { + RegionKey key = new RegionKey(scope, region.RegionID); + return Contains(key); + } + + public bool Contains(RegionKey key) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + return timedStorage.ContainsKey(key); + } + finally { Monitor.Exit(syncRoot); } + } + + public int Count + { + get + { + return timedStorage.Count; + } + } + + public bool Remove(UUID scope, GridRegion region) + { + RegionKey key = new RegionKey(scope, region.RegionID); + + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + if (timedStorage.ContainsKey(key)) + { + RegionInfoByScope ris = null; + if(InfobyScope.TryGetValue(scope, out ris) && ris != null) + { + GridRegion r = timedStorage[key]; + if(r != null) + ris.RemoveRegion(r); + if(ris.Count() == 0) + InfobyScope.Remove(scope); + } + timedStorage.Remove(key); + return true; + } + else + return false; + } + finally { Monitor.Exit(syncRoot); } + } + + public bool TryGetValue(RegionKey key, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + if (timedStorage.ContainsKey(key)) + { + value = timedStorage[key]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + value = null; + return false; + } + + public bool TryGetValue(UUID scope, UUID id, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + RegionKey rk = new RegionKey(scope, id); + if(timedStorage.ContainsKey(rk)) + { + value = timedStorage[rk]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + value = null; + return false; + } + + public bool TryGetValue(UUID scope, string name, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + value = null; + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + return false; + + RegionKey key = ris.get(name); + if(key == null) + return false; + + if(timedStorage.ContainsKey(key)) + { + value = timedStorage[key]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + return false; + } + + public bool TryGetValue(UUID scope, ulong handle, out GridRegion value) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + try + { + value = null; + RegionInfoByScope ris = null; + if(!InfobyScope.TryGetValue(scope, out ris) || ris == null) + return false; + + RegionKey key = ris.get(handle); + if(key == null) + return false; + + if(timedStorage.ContainsKey(key)) + { + value = timedStorage[key]; + return true; + } + } + finally { Monitor.Exit(syncRoot); } + + value = null; + return false; + } + + public bool Update(UUID scope, GridRegion region, double expirationSeconds) + { + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms"); + + RegionKey key = new RegionKey(scope, region.RegionID); + try + { + if (!timedStorage.ContainsKey(key)) + return false; + + timedStorage.Remove(key); + key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds); + timedStorage.Add(key, region); + return true; + } + finally { Monitor.Exit(syncRoot); } + } + + /// + /// Purges expired objects from the cache. Called automatically by the purge timer. + /// + private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e) + { + // Only let one thread purge at once - a buildup could cause a crash + // This could cause the purge to be delayed while there are lots of read/write ops + // happening on the cache + if (!Monitor.TryEnter(isPurging)) + return; + + DateTime signalTime = DateTime.UtcNow; + + try + { + // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle + if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT)) + return; + try + { + OpenMetaverse.Lazy> expiredItems = new OpenMetaverse.Lazy>(); + + foreach (RegionKey timedKey in timedStorage.Keys) + { + if (timedKey.ExpirationDate < signalTime) + { + // Mark the object for purge + expiredItems.Value.Add(timedKey); + } + else + { + break; + } + } + + + RegionInfoByScope ris; + if (expiredItems.IsValueCreated) + { + foreach (RegionKey key in expiredItems.Value) + { + ris = null; + if(InfobyScope.TryGetValue(key.ScopeID, out ris) && ris != null) + { + GridRegion r = timedStorage[key]; + if(r != null) + ris.RemoveRegion(r); + + if(ris.Count() == 0) + InfobyScope.Remove(key.ScopeID); + } + timedStorage.Remove(key); + } + } + } + finally { Monitor.Exit(syncRoot); } + } + finally { Monitor.Exit(isPurging); } + } + } }