diff --git a/OpenSim/Framework/IAssetCache.cs b/OpenSim/Framework/IAssetCache.cs
index 855b86b15a..8477116403 100644
--- a/OpenSim/Framework/IAssetCache.cs
+++ b/OpenSim/Framework/IAssetCache.cs
@@ -38,6 +38,12 @@ namespace OpenSim.Framework
void Cache(AssetBase asset);
///
+ /// Cache that the specified asset wasn't found.
+ ///
+ ///
+ ///
+ void CacheNegative(string id);
+
/// Get an asset by its id.
///
///
diff --git a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs
index 382b5f2821..14b0280cd5 100644
--- a/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs
+++ b/OpenSim/Region/CoreModules/Asset/CenomeAssetCache.cs
@@ -221,6 +221,11 @@ namespace OpenSim.Region.CoreModules.Asset
}
+ public void CacheNegative(string id)
+ {
+ // We don't do negative caching
+ }
+
///
/// Clear asset cache.
///
diff --git a/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs b/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs
index d78b082366..82bc5ccebb 100644
--- a/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs
+++ b/OpenSim/Region/CoreModules/Asset/CoreAssetCache.cs
@@ -124,6 +124,11 @@ namespace OpenSim.Region.CoreModules.Asset
m_Cache.Store(asset.ID, asset);
}
+ public void CacheNegative(string id)
+ {
+ // We don't do negative caching
+ }
+
public AssetBase Get(string id)
{
return (AssetBase)m_Cache.Get(id);
diff --git a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs
index fcd42524d2..84e13a0d9d 100644
--- a/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs
+++ b/OpenSim/Region/CoreModules/Asset/FlotsamAssetCache.cs
@@ -92,9 +92,15 @@ namespace OpenSim.Region.CoreModules.Asset
private ExpiringCache m_MemoryCache;
private bool m_MemoryCacheEnabled = false;
+ private ExpiringCache m_negativeCache;
+ private bool m_negativeCacheEnabled = true;
+ private bool m_negativeCacheSliding = false;
+
// Expiration is expressed in hours.
private double m_MemoryExpiration = 0.016;
private const double m_DefaultFileExpiration = 48;
+ // Negative cache is in seconds
+ private int m_negativeExpiration = 120;
private TimeSpan m_FileExpiration = TimeSpan.FromHours(m_DefaultFileExpiration);
private TimeSpan m_FileExpirationCleanupTimer = TimeSpan.FromHours(1.0);
@@ -139,6 +145,7 @@ namespace OpenSim.Region.CoreModules.Asset
if (name == Name)
{
m_MemoryCache = new ExpiringCache();
+ m_negativeCache = new ExpiringCache();
m_Enabled = true;
m_log.InfoFormat("[FLOTSAM ASSET CACHE]: {0} enabled", this.Name);
@@ -158,6 +165,9 @@ namespace OpenSim.Region.CoreModules.Asset
m_MemoryExpiration = assetConfig.GetDouble("MemoryCacheTimeout", m_MemoryExpiration);
m_MemoryExpiration *= 3600.0; // config in hours to seconds
+ m_negativeCacheEnabled = assetConfig.GetBoolean("NegativeCacheEnabled", m_negativeCacheEnabled);
+ m_negativeExpiration = assetConfig.GetInt("NegativeCacheTimeout", m_negativeExpiration);
+ m_negativeCacheSliding = assetConfig.GetBoolean("NegativeCacheSliding", m_negativeCacheSliding);
m_updateFileTimeOnCacheHit = assetConfig.GetBoolean("UpdateFileTimeOnCacheHit", m_updateFileTimeOnCacheHit);
#if WAIT_ON_INPROGRESS_REQUESTS
@@ -355,6 +365,17 @@ namespace OpenSim.Region.CoreModules.Asset
}
}
+ public void CacheNegative(string id)
+ {
+ if (m_negativeCacheEnabled)
+ {
+ if (m_negativeCacheSliding)
+ m_negativeCache.AddOrUpdate(id, null, TimeSpan.FromSeconds(m_negativeExpiration));
+ else
+ m_negativeCache.AddOrUpdate(id, null, m_negativeExpiration);
+ }
+ }
+
///
/// Updates the cached file with the current time.
///
@@ -514,6 +535,10 @@ namespace OpenSim.Region.CoreModules.Asset
{
m_Requests++;
+ object dummy;
+ if (m_negativeCache.TryGetValue(id, out dummy))
+ return null;
+
AssetBase asset = null;
asset = GetFromWeakReference(id);
if (asset != null && m_updateFileTimeOnCacheHit)
@@ -622,6 +647,8 @@ namespace OpenSim.Region.CoreModules.Asset
if (m_MemoryCacheEnabled)
m_MemoryCache = new ExpiringCache();
+ if (m_negativeCacheEnabled)
+ m_negativeCache = new ExpiringCache();
lock(weakAssetReferencesLock)
weakAssetReferences = new Dictionary();
diff --git a/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs b/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs
index 7343f4fbb8..195bdaa43f 100644
--- a/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs
+++ b/OpenSim/Region/CoreModules/Asset/GlynnTuckerAssetCache.cs
@@ -126,6 +126,11 @@ namespace OpenSim.Region.CoreModules.Asset
m_Cache.AddOrUpdate(asset.ID, asset);
}
+ public void CacheNegative(string id)
+ {
+ // We don't do negative caching
+ }
+
public AssetBase Get(string id)
{
Object asset = null;
diff --git a/OpenSim/Services/Connectors/Asset/AssetServicesConnector.cs b/OpenSim/Services/Connectors/Asset/AssetServicesConnector.cs
index b8449d7ce4..bdc3befb08 100644
--- a/OpenSim/Services/Connectors/Asset/AssetServicesConnector.cs
+++ b/OpenSim/Services/Connectors/Asset/AssetServicesConnector.cs
@@ -260,8 +260,13 @@ namespace OpenSim.Services.Connectors
asset = SynchronousRestObjectRequester.MakeRequest("GET", uri, 0, m_Auth);
- if (asset != null && m_Cache != null)
- m_Cache.Cache(asset);
+ if (m_Cache != null)
+ {
+ if (asset != null)
+ m_Cache.Cache(asset);
+ else
+ m_Cache.CacheNegative(id);
+ }
}
return asset;
}
diff --git a/bin/config-include/FlotsamCache.ini.example b/bin/config-include/FlotsamCache.ini.example
index 917ea4607c..2b5d37e0e0 100644
--- a/bin/config-include/FlotsamCache.ini.example
+++ b/bin/config-include/FlotsamCache.ini.example
@@ -24,6 +24,43 @@
; so even a small memory cache is useful
MemoryCacheEnabled = false
+ ; If a memory cache hit happens, or the asset is still in memory
+ ; due to other causes, update the timestamp on the disk file anyway.
+ ; Don't turn this on unless you share your asset cache between simulators
+ ; AND use an external process, e.g. cron job, to clean it up.
+ UpdateFileTimeOnCacheHit = false
+
+ ; Enabling this will cache negative fetches. If an asset is negative-cached
+ ; it will not be re-requested from the asset server again for a while.
+ ; Generally, this is a good thing.
+ ;
+ ; Regular expiration settings (non-sliding) mean that the asset will be
+ ; retried after the time has expired. Sliding expiration means that
+ ; the time the negative cache will keep the asset is refreshed each
+ ; time a fetch is attempted. Use sliding expiration if you have rogue
+ ; scripts hammering the asset server with requests for nonexistent
+ ; assets.
+ ;
+ ; There are two cases where negative caching may cause issues:
+ ;
+ ; 1 - If an invalid asset is repeatedly requested by a script and that asset is
+ ; subsequently created, it will not be seen until fcache clear
+ ; is used. This is a very theoretical scenario since UUID collisions
+ ; are deemed to be not occuring in practice.
+ ; This can only become an issue with sliding expiration time.
+ ;
+ ; 2 - If the asset service is clustered, an asset may not have propagated
+ ; to all cluster members when it is first attempted to fetch it.
+ ; This may theoretically occur with networked vendor systems and
+ ; would lead to an asset not found message. However, after the
+ ; expiration time has elapsed, the asset will the be fetchable.
+ ;
+ ; The defaults below are suitable for all small to medium installations
+ ; including grids.
+ NegativeCacheEnabled = true
+ NegativeCacheTimeout = 120
+ NegativeCacheSliding = false
+
; Set to false for no file cache
FileCacheEnabled = true