Skip to content
This repository has been archived by the owner on Nov 22, 2018. It is now read-only.

Commit

Permalink
Handle cache unreliability #99
Browse files Browse the repository at this point in the history
  • Loading branch information
JunTaoLuo committed May 23, 2016
1 parent dabd28a commit d61c510
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 176 deletions.
4 changes: 2 additions & 2 deletions samples/SessionSample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"Hosting:Environment": "Development"
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SessionSample": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000/",
"launchUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
Expand Down
7 changes: 3 additions & 4 deletions samples/SessionSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public Startup(ILoggerFactory loggerFactory)

public void ConfigureServices(IServiceCollection services)
{
// Adds a default in-memory implementation of IDistributedCache
services.AddDistributedMemoryCache();

// Uncomment the following line to use the Microsoft SQL Server implementation of IDistributedCache.
// Note that this would require setting up the session state database.
//services.AddSqlServerCache(o =>
Expand All @@ -32,10 +35,6 @@ public void ConfigureServices(IServiceCollection services)
// This will override any previously registered IDistributedCache service.
//services.AddSingleton<IDistributedCache, RedisCache>();
#endif
// Adds a default in-memory implementation of IDistributedCache
services.AddMemoryCache();
services.AddDistributedMemoryCache();

services.AddSession(o =>
{
o.IdleTimeout = TimeSpan.FromSeconds(10);
Expand Down
178 changes: 69 additions & 109 deletions src/Microsoft.AspNetCore.Session/DistributedSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
Expand All @@ -26,10 +25,11 @@ public class DistributedSession : ISession
private readonly string _sessionKey;
private readonly TimeSpan _idleTimeout;
private readonly Func<bool> _tryEstablishSession;
private readonly IDictionary<EncodedKey, byte[]> _store;
private readonly ILogger _logger;
private IDictionary<EncodedKey, byte[]> _store;
private bool _isModified;
private bool _loaded;
private bool _isAvailable;
private bool _isNewSessionKey;
private string _sessionId;
private byte[] _sessionIdBytes;
Expand Down Expand Up @@ -71,11 +71,20 @@ public class DistributedSession : ISession
_isNewSessionKey = isNewSessionKey;
}

public bool IsAvailable
{
get
{
Load();
return _isAvailable;
}
}

public string Id
{
get
{
Load(); // TODO: Silent failure
Load();
if (_sessionId == null)
{
_sessionId = new Guid(IdBytes).ToString();
Expand All @@ -88,8 +97,7 @@ private byte[] IdBytes
{
get
{
Load(); // TODO: Silent failure
if (_sessionIdBytes == null)
if (IsAvailable && _sessionIdBytes == null)
{
_sessionIdBytes = new byte[IdByteCount];
CryptoRandom.GetBytes(_sessionIdBytes);
Expand All @@ -102,14 +110,14 @@ public IEnumerable<string> Keys
{
get
{
Load(); // TODO: Silent failure
Load();
return _store.Keys.Select(key => key.KeyString);
}
}

public bool TryGetValue(string key, out byte[] value)
{
Load(); // TODO: Silent failure
Load();
return _store.TryGetValue(new EncodedKey(key), out value);
}

Expand All @@ -120,22 +128,24 @@ public void Set(string key, byte[] value)
throw new ArgumentNullException(nameof(value));
}

var encodedKey = new EncodedKey(key);
if (encodedKey.KeyBytes.Length > KeyLengthLimit)
if (IsAvailable)
{
throw new ArgumentOutOfRangeException(nameof(key),
Resources.FormatException_KeyLengthIsExceeded(KeyLengthLimit));
}
var encodedKey = new EncodedKey(key);
if (encodedKey.KeyBytes.Length > KeyLengthLimit)
{
throw new ArgumentOutOfRangeException(nameof(key),
Resources.FormatException_KeyLengthIsExceeded(KeyLengthLimit));
}

Load();
if (!_tryEstablishSession())
{
throw new InvalidOperationException(Resources.Exception_InvalidSessionEstablishment);
if (!_tryEstablishSession())
{
throw new InvalidOperationException(Resources.Exception_InvalidSessionEstablishment);
}
_isModified = true;
byte[] copy = new byte[value.Length];
Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length);
_store[encodedKey] = copy;
}
_isModified = true;
byte[] copy = new byte[value.Length];
Buffer.BlockCopy(src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length);
_store[encodedKey] = copy;
}

public void Remove(string key)
Expand All @@ -155,21 +165,35 @@ private void Load()
{
if (!_loaded)
{
var data = _cache.Get(_sessionKey);
if (data != null)
try
{
Deserialize(new MemoryStream(data));
var data = _cache.Get(_sessionKey);
if (data != null)
{
Deserialize(new MemoryStream(data));
}
else if (!_isNewSessionKey)
{
_logger.AccessingExpiredSession(_sessionKey);
}
_isAvailable = true;
}
else if (!_isNewSessionKey)
catch (Exception exception)
{
_logger.AccessingExpiredSession(_sessionKey);
_logger.SessionCacheReadException(_sessionKey, exception);
_isAvailable = false;
_sessionId = string.Empty;
_sessionIdBytes = null;
_store = new NoOpSessionStore();
}
finally
{
_loaded = true;
}
_loaded = true;
}
}

// TODO: This should throw if called directly, but most other places it should fail silently
// (e.g. TryGetValue should just return null).
// This will throw if called directly and a failure occurs. The user is expected to handle the failures.
public async Task LoadAsync()
{
if (!_loaded)
Expand All @@ -183,6 +207,7 @@ public async Task LoadAsync()
{
_logger.AccessingExpiredSession(_sessionKey);
}
_isAvailable = true;
_loaded = true;
}
}
Expand All @@ -191,24 +216,32 @@ public async Task CommitAsync()
{
if (_isModified)
{
var data = await _cache.GetAsync(_sessionKey);
if (_logger.IsEnabled(LogLevel.Information) && data == null)
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.SessionStarted(_sessionKey, Id);
try
{
var data = await _cache.GetAsync(_sessionKey);
if (data == null)
{
_logger.SessionStarted(_sessionKey, Id);
}
}
catch (Exception exception)
{
_logger.SessionCacheReadException(_sessionKey, exception);
}
}
_isModified = false;

var stream = new MemoryStream();
Serialize(stream);

await _cache.SetAsync(
_sessionKey,
stream.ToArray(),
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));

if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.SessionStored(_sessionKey, Id, _store.Count);
}
_isModified = false;
_logger.SessionStored(_sessionKey, Id, _store.Count);
}
else
{
Expand Down Expand Up @@ -245,7 +278,6 @@ private void Deserialize(Stream content)
{
if (content == null || content.ReadByte() != SerializationRevision)
{
// TODO: Throw?
// Replace the un-readable format.
_isModified = true;
return;
Expand Down Expand Up @@ -333,77 +365,5 @@ private byte[] ReadBytes(Stream stream, int count)
return output;
}

// Keys are stored in their utf-8 encoded state.
// This saves us from de-serializing and re-serializing every key on every request.
private class EncodedKey
{
private string _keyString;
private int? _hashCode;

internal EncodedKey(string key)
{
_keyString = key;
KeyBytes = Encoding.UTF8.GetBytes(key);
}

public EncodedKey(byte[] key)
{
KeyBytes = key;
}

internal string KeyString
{
get
{
if (_keyString == null)
{
_keyString = Encoding.UTF8.GetString(KeyBytes, 0, KeyBytes.Length);
}
return _keyString;
}
}

internal byte[] KeyBytes { get; private set; }

public override bool Equals(object obj)
{
var otherKey = obj as EncodedKey;
if (otherKey == null)
{
return false;
}
if (KeyBytes.Length != otherKey.KeyBytes.Length)
{
return false;
}
if (_hashCode.HasValue && otherKey._hashCode.HasValue
&& _hashCode.Value != otherKey._hashCode.Value)
{
return false;
}
for (int i = 0; i < KeyBytes.Length; i++)
{
if (KeyBytes[i] != otherKey.KeyBytes[i])
{
return false;
}
}
return true;
}

public override int GetHashCode()
{
if (!_hashCode.HasValue)
{
_hashCode = SipHash.GetHashCode(KeyBytes);
}
return _hashCode.Value;
}

public override string ToString()
{
return KeyString;
}
}
}
}
8 changes: 0 additions & 8 deletions src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@ public DistributedSessionStore(IDistributedCache cache, ILoggerFactory loggerFac
_loggerFactory = loggerFactory;
}

public bool IsAvailable
{
get
{
return true; // TODO:
}
}

public ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
{
if (string.IsNullOrEmpty(sessionKey))
Expand Down

0 comments on commit d61c510

Please sign in to comment.