/* * Copyright (c) 2008 Intel Corporation * 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. * -- 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 Intel Corporation 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 INTEL OR ITS * 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 BeIT.MemCached; using ExtensionLoader; using ExtensionLoader.Config; using OpenMetaverse; namespace AssetServer.Extensions { public class MemcachedStorage : IExtension, IStorageProvider { const string EXTENSION_NAME = "MemcachedStorage"; // Used in metrics reporting AssetServer server; MemcachedClient cacheClient; IStorageProvider storageBackend; public MemcachedStorage() { } #region Required Interfaces public void Start(AssetServer server) { this.server = server; cacheClient = null; try { IConfig memcacheConfig = server.ConfigFile.Configs["Memcached"]; if (memcacheConfig.Contains("Servers")) { string[] servers = memcacheConfig.GetString("Servers").Split(','); try { MemcachedClient.Setup("AssetServer", servers); cacheClient = MemcachedClient.GetInstance("AssetServer"); } catch (Exception ex) { Logger.Log.Error("Failed to connect to memcached: " + ex.Message); } } } catch (Exception) { Logger.Log.Error("Failed to load [Memcached] section from config file " + AssetServer.CONFIG_FILE); } // Search through the loaded extensions for another IStorageProvider // that will serve as the fallback for the cache foreach (IExtension extension in ExtensionLoader.Extensions) { if (extension is IStorageProvider && extension != this) storageBackend = extension as IStorageProvider; } if (storageBackend == null) Logger.Log.Warn("Memcached caching storage provider loaded with no fallback storage provider"); } public void Stop() { } public BackendResponse TryFetchMetadata(UUID assetID, out Metadata metadata) { metadata = null; BackendResponse ret; string cacheID = assetID.ToString() + "-metadata"; if (cacheClient != null) { byte[] data = cacheClient.Get(cacheID) as byte[]; if (data != null) { metadata = new Metadata(); metadata.Deserialize(data); } } if (metadata != null) { ret = BackendResponse.Success; } else if (storageBackend != null) { BackendResponse response = storageBackend.TryFetchMetadata(assetID, out metadata); if (response == BackendResponse.Success && cacheClient != null) { if (cacheClient.Set(cacheID, metadata.SerializeToBytes())) Logger.Log.Debug("Cached " + cacheID); } ret = response; } else { ret = BackendResponse.Failure; } server.MetricsProvider.LogAssetMetadataFetch(EXTENSION_NAME, ret, assetID, DateTime.Now); return ret; } public BackendResponse TryFetchData(UUID assetID, out byte[] assetData) { assetData = null; BackendResponse ret; string cacheID = assetID.ToString(); if (cacheClient != null) assetData = cacheClient.Get(cacheID) as byte[]; if (assetData != null) { ret = BackendResponse.Success; } else if (storageBackend != null) { BackendResponse response = storageBackend.TryFetchData(assetID, out assetData); if (response == BackendResponse.Success && cacheClient != null) { if (cacheClient.Set(cacheID, assetData)) Logger.Log.Debug("Cached " + cacheID); } ret = response; } else { ret = BackendResponse.Failure; } server.MetricsProvider.LogAssetDataFetch(EXTENSION_NAME, ret, assetID, (assetData != null ? assetData.Length : 0), DateTime.Now); return ret; } public BackendResponse TryFetchDataMetadata(UUID assetID, out Metadata metadata, out byte[] assetData) { metadata = null; BackendResponse response = TryFetchData(assetID, out assetData); if (response == BackendResponse.Success) response = TryFetchMetadata(assetID, out metadata); return response; } public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData, out UUID assetID) { assetID = metadata.ID = UUID.Random(); return TryCreateAsset(metadata, assetData); } public BackendResponse TryCreateAsset(Metadata metadata, byte[] assetData) { BackendResponse ret; if (storageBackend != null) { BackendResponse response = storageBackend.TryCreateAsset(metadata, assetData); if (response == BackendResponse.Success && cacheClient != null) { // There is a high likelihood that an asset that was just uploaded (or its metdata) // will be fetched very soon. Call TryFetchDataMetadata to pre-emptively cache the // final stored data TryFetchDataMetadata(metadata.ID, out metadata, out assetData); } ret = response; } else { ret = BackendResponse.Failure; } // Don't log the storage to the metrics provider, the actual storage provider will do that return ret; } public int ForEach(Action action, int start, int count) { // Can't iterate over memcached contents, pass this directly to the backend if (storageBackend != null) return storageBackend.ForEach(action, start, count); else return 0; } #endregion Required Interfaces } }