| using Apache.Geode.DotNetCore; |
| using Microsoft.Extensions.Caching.Distributed; |
| using Microsoft.Extensions.Logging; |
| using System; |
| using System.Threading; |
| using System.Threading.Tasks; |
| |
| namespace Apache.Geode.Session |
| { |
| public class SessionStateValue |
| { |
| DateTime _lastAccessTimeUtc; |
| DateTime _expirationTimeUtc = DateTime.MinValue; |
| TimeSpan _spanUntilStale = TimeSpan.Zero; |
| private byte[] _value; |
| |
| public SessionStateValue() { } |
| public SessionStateValue(byte[] value) |
| { |
| FromByteArray(value); |
| } |
| |
| public byte[] Value |
| { |
| get { return _value; } |
| set { _value = value; } |
| } |
| public DateTime LastAccessTimeUtc |
| { |
| get { return _lastAccessTimeUtc; } |
| set { _lastAccessTimeUtc = value; } |
| } |
| |
| public DateTime ExpirationTimeUtc |
| { |
| get { return _expirationTimeUtc; } |
| set { _expirationTimeUtc = value; } |
| } |
| |
| public TimeSpan SpanUntilStale |
| { |
| get { return _spanUntilStale; } |
| set { _spanUntilStale = value; } |
| } |
| |
| public byte[] ToByteArray() |
| { |
| int neededBytes = 3*sizeof(long) + _value.Length; |
| byte[] byteArray = new byte[neededBytes]; |
| int byteIndex = 0; |
| |
| // Append LastAccessTimeUtc |
| Array.Copy(BitConverter.GetBytes(LastAccessTimeUtc.Ticks), 0, byteArray, byteIndex, sizeof(long)); |
| byteIndex += sizeof(long); |
| |
| // Append ExpirationTimeUtc |
| Array.Copy(BitConverter.GetBytes(ExpirationTimeUtc.Ticks), 0, byteArray, byteIndex, sizeof(long)); |
| byteIndex += sizeof(long); |
| |
| // Append SpanUntilStale |
| Array.Copy(BitConverter.GetBytes(SpanUntilStale.Ticks), 0, byteArray, byteIndex, sizeof(long)); |
| byteIndex += sizeof(long); |
| |
| // Append the value |
| Array.Copy(_value, 0, byteArray, byteIndex, _value.Length); |
| return byteArray; |
| } |
| |
| public void FromByteArray(byte[] data) |
| { |
| int byteIndex = 0; |
| |
| // Extract the LastAccessTimeUtc |
| LastAccessTimeUtc = DateTime.FromBinary(BitConverter.ToInt64(data, byteIndex)); |
| byteIndex += sizeof(long); |
| |
| // Extract the ExpirationTimeUtc |
| ExpirationTimeUtc = DateTime.FromBinary(BitConverter.ToInt64(data, byteIndex)); |
| byteIndex += sizeof(long); |
| |
| // Extract the SpanUntilStale |
| SpanUntilStale = TimeSpan.FromTicks(BitConverter.ToInt64(data, byteIndex)); |
| byteIndex += sizeof(long); |
| |
| // Extract the value |
| Value = new byte[data.Length - byteIndex]; |
| Array.Copy(data, byteIndex, _value, 0, data.Length - byteIndex); |
| } |
| } |
| |
| public class SessionStateCache : IDistributedCache |
| { |
| private readonly Cache _cache; |
| private ILogger<SessionStateCache> _logger; |
| private static Region _region; |
| private string _regionName; |
| private readonly SemaphoreSlim _connectLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); |
| |
| public SessionStateCache(Cache cache, string regionName, ILogger<SessionStateCache> logger = null) |
| { |
| _regionName = regionName ?? throw new ArgumentNullException(regionName); |
| _cache = cache ?? throw new ArgumentNullException(nameof(cache)); |
| _logger = logger; |
| |
| _cache.PoolFactory.AddLocator("localhost", 10334); |
| _cache.PoolFactory.CreatePool("pool"); |
| } |
| |
| // Returns the SessionStateValue for key, or null if key doesn't exist |
| public SessionStateValue GetValueForKey(string key) |
| { |
| byte[] cacheValue = _region.GetByteArray(key); |
| |
| if (cacheValue != null) |
| { |
| return new SessionStateValue(cacheValue); |
| } |
| else |
| return null; |
| } |
| |
| public byte[] Get(string key) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| Connect(); |
| |
| // Check for nonexistent key |
| SessionStateValue ssValue = GetValueForKey(key); |
| if (ssValue == null) |
| return null; |
| |
| // Check for expired key |
| DateTime nowUtc = DateTime.UtcNow; |
| if (ssValue.ExpirationTimeUtc != DateTime.MinValue && ssValue.ExpirationTimeUtc < nowUtc) |
| return null; |
| |
| // Check for stale key |
| if (ssValue.SpanUntilStale != TimeSpan.Zero && |
| nowUtc > (ssValue.LastAccessTimeUtc + ssValue.SpanUntilStale)) |
| return null; |
| |
| //LogDebug("Inserting against key [" + key + "] with absolute expiration: " + |
| // options.AbsoluteExpiration.Value.DateTime); |
| |
| // Update the times for sliding expirations |
| if (ssValue.SpanUntilStale != TimeSpan.Zero) |
| { |
| ssValue.LastAccessTimeUtc = nowUtc; |
| _region.PutByteArray(key, ssValue.ToByteArray()); |
| } |
| |
| return ssValue.Value; |
| } |
| |
| public Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken)) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| token.ThrowIfCancellationRequested(); |
| |
| return Task.Factory.StartNew(() => Get(key), token); |
| } |
| |
| public void Refresh(string key) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| Connect(); |
| |
| // Check for nonexistent key |
| SessionStateValue ssValue = GetValueForKey(key); |
| if (ssValue == null) |
| return; |
| |
| // Check for expired key |
| DateTime nowUtc = DateTime.UtcNow; |
| if (ssValue.ExpirationTimeUtc != DateTime.MinValue && ssValue.ExpirationTimeUtc < nowUtc) |
| return; |
| |
| // Check for stale key |
| if (ssValue.SpanUntilStale != TimeSpan.Zero && |
| nowUtc > (ssValue.LastAccessTimeUtc + ssValue.SpanUntilStale)) |
| return; |
| |
| //LogDebug("Inserting against key [" + key + "] with absolute expiration: " + |
| // options.AbsoluteExpiration.Value.DateTime); |
| |
| // Update the times for sliding expirations |
| if (ssValue.SpanUntilStale != TimeSpan.Zero) |
| { |
| ssValue.LastAccessTimeUtc = nowUtc; |
| _region.PutByteArray(key, ssValue.ToByteArray()); |
| } |
| } |
| |
| public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| token.ThrowIfCancellationRequested(); |
| |
| return Task.Factory.StartNew(() => Refresh(key), token); |
| } |
| |
| public void Remove(string key) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| Connect(); |
| |
| // Until we return error codes |
| //if (!_cacheRegion.Remove(key)) |
| //{ |
| // throw new Exception("Failed to remove from cache"); |
| //} |
| _region.Remove(key); |
| } |
| |
| public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| token.ThrowIfCancellationRequested(); |
| |
| return Task.Factory.StartNew(() => Remove(key), token); |
| } |
| |
| public void Set(string key, byte[] value, DistributedCacheEntryOptions options) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| if (value == null) |
| { |
| throw new ArgumentNullException(nameof(value)); |
| } |
| |
| if (options == null) |
| { |
| throw new ArgumentNullException(nameof(options)); |
| } |
| |
| Connect(); |
| |
| SessionStateValue ssValue = new SessionStateValue(); |
| ssValue.Value = value; |
| |
| DateTime nowUtc = DateTime.UtcNow; |
| ssValue.LastAccessTimeUtc = nowUtc; |
| |
| // No need to check stale or expired data when setting an absolute expiration. |
| // Think of if as setting a new key/value pair. Expired data will always be cleaned up |
| // when the CleanupExpiredData job runs. |
| |
| if (options.AbsoluteExpiration != null) |
| { |
| //LogDebug("Inserting against key [" + key + "] with absolute expiration: " + |
| // options.AbsoluteExpiration.Value.DateTime); |
| DateTimeOffset dto = options.AbsoluteExpiration.Value; |
| ssValue.ExpirationTimeUtc = dto.DateTime + dto.Offset; |
| } |
| |
| // If AbsoluteExpiration and AbsoluteExpirationRelativeToNow are set, use the latter. |
| if (options.AbsoluteExpirationRelativeToNow != null) |
| { |
| //LogDebug("Inserting against key [" + key + "] with absolute expiration: " + |
| // options.AbsoluteExpiration.Value.DateTime); |
| TimeSpan ts = options.AbsoluteExpirationRelativeToNow.Value; |
| ssValue.ExpirationTimeUtc = nowUtc + ts; |
| } |
| |
| if (options.SlidingExpiration != null) |
| { |
| //LogDebug("Inserting against key [" + key + "] with absolute expiration: " + |
| // options.AbsoluteExpiration.Value.DateTime); |
| ssValue.SpanUntilStale = options.SlidingExpiration.Value; |
| } |
| |
| _region.PutByteArray(key, ssValue.ToByteArray()); |
| return; |
| } |
| |
| public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) |
| { |
| if (key == null) |
| { |
| throw new ArgumentNullException(nameof(key)); |
| } |
| |
| token.ThrowIfCancellationRequested(); |
| |
| return Task.Factory.StartNew(() => Set(key, value, options), token); |
| } |
| |
| private void Connect() |
| { |
| if (_region != null) |
| { |
| return; |
| } |
| |
| _connectLock.Wait(); |
| try |
| { |
| var regionFactory = _cache.CreateRegionFactory(RegionShortcut.Proxy); |
| try |
| { |
| _logger?.LogTrace("Create CacheRegion"); |
| _region = regionFactory.CreateRegion(_regionName); |
| _logger?.LogTrace("CacheRegion created"); |
| } |
| catch (Exception e) |
| { |
| _logger?.LogInformation(e, "Create CacheRegion failed... now trying to get the region"); |
| } |
| } |
| finally |
| { |
| _connectLock.Release(); |
| } |
| } |
| } |
| } |