#167 Add cancellation tokens and defualt timeouts for Load/CommitAsync.

This commit is contained in:
Chris R 2017-06-28 15:44:43 -07:00
parent 6f4f34a0bf
commit 1a1e4c309f
6 changed files with 93 additions and 39 deletions

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.Session
private readonly IDistributedCache _cache; private readonly IDistributedCache _cache;
private readonly string _sessionKey; private readonly string _sessionKey;
private readonly TimeSpan _idleTimeout; private readonly TimeSpan _idleTimeout;
private readonly TimeSpan _ioTimeout;
private readonly Func<bool> _tryEstablishSession; private readonly Func<bool> _tryEstablishSession;
private readonly ILogger _logger; private readonly ILogger _logger;
private IDictionary<EncodedKey, byte[]> _store; private IDictionary<EncodedKey, byte[]> _store;
@ -38,6 +40,7 @@ namespace Microsoft.AspNetCore.Session
IDistributedCache cache, IDistributedCache cache,
string sessionKey, string sessionKey,
TimeSpan idleTimeout, TimeSpan idleTimeout,
TimeSpan ioTimeout,
Func<bool> tryEstablishSession, Func<bool> tryEstablishSession,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
bool isNewSessionKey) bool isNewSessionKey)
@ -65,6 +68,7 @@ namespace Microsoft.AspNetCore.Session
_cache = cache; _cache = cache;
_sessionKey = sessionKey; _sessionKey = sessionKey;
_idleTimeout = idleTimeout; _idleTimeout = idleTimeout;
_ioTimeout = ioTimeout;
_tryEstablishSession = tryEstablishSession; _tryEstablishSession = tryEstablishSession;
_store = new Dictionary<EncodedKey, byte[]>(); _store = new Dictionary<EncodedKey, byte[]>();
_logger = loggerFactory.CreateLogger<DistributedSession>(); _logger = loggerFactory.CreateLogger<DistributedSession>();
@ -194,58 +198,68 @@ namespace Microsoft.AspNetCore.Session
} }
// This will throw if called directly and a failure occurs. The user is expected to handle the failures. // This will throw if called directly and a failure occurs. The user is expected to handle the failures.
public async Task LoadAsync() public async Task LoadAsync(CancellationToken cancellationToken = default(CancellationToken))
{ {
cancellationToken.ThrowIfCancellationRequested();
if (!_loaded) if (!_loaded)
{ {
var data = await _cache.GetAsync(_sessionKey); using (var timeout = new CancellationTokenSource(_ioTimeout))
if (data != null)
{ {
Deserialize(new MemoryStream(data)); var cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
} var data = await _cache.GetAsync(_sessionKey, cts.Token);
else if (!_isNewSessionKey) if (data != null)
{ {
_logger.AccessingExpiredSession(_sessionKey); Deserialize(new MemoryStream(data));
}
else if (!_isNewSessionKey)
{
_logger.AccessingExpiredSession(_sessionKey);
}
} }
_isAvailable = true; _isAvailable = true;
_loaded = true; _loaded = true;
} }
} }
public async Task CommitAsync() public async Task CommitAsync(CancellationToken cancellationToken = default(CancellationToken))
{ {
if (_isModified) cancellationToken.ThrowIfCancellationRequested();
using (var timeout = new CancellationTokenSource(_ioTimeout))
{ {
if (_logger.IsEnabled(LogLevel.Information)) var cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken);
if (_isModified)
{ {
try if (_logger.IsEnabled(LogLevel.Information))
{ {
var data = await _cache.GetAsync(_sessionKey); try
if (data == null)
{ {
_logger.SessionStarted(_sessionKey, Id); var data = await _cache.GetAsync(_sessionKey, cts.Token);
if (data == null)
{
_logger.SessionStarted(_sessionKey, Id);
}
}
catch (Exception exception)
{
_logger.SessionCacheReadException(_sessionKey, exception);
} }
} }
catch (Exception exception)
{ var stream = new MemoryStream();
_logger.SessionCacheReadException(_sessionKey, exception); Serialize(stream);
}
await _cache.SetAsync(
_sessionKey,
stream.ToArray(),
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout),
cts.Token);
_isModified = false;
_logger.SessionStored(_sessionKey, Id, _store.Count);
}
else
{
await _cache.RefreshAsync(_sessionKey, cts.Token);
} }
var stream = new MemoryStream();
Serialize(stream);
await _cache.SetAsync(
_sessionKey,
stream.ToArray(),
new DistributedCacheEntryOptions().SetSlidingExpiration(_idleTimeout));
_isModified = false;
_logger.SessionStored(_sessionKey, Id, _store.Count);
}
else
{
await _cache.RefreshAsync(_sessionKey);
} }
} }

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Session
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
} }
public ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey) public ISession Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
{ {
if (string.IsNullOrEmpty(sessionKey)) if (string.IsNullOrEmpty(sessionKey))
{ {
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Session
throw new ArgumentNullException(nameof(tryEstablishSession)); throw new ArgumentNullException(nameof(tryEstablishSession));
} }
return new DistributedSession(_cache, sessionKey, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey); return new DistributedSession(_cache, sessionKey, idleTimeout, ioTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
} }
} }
} }

View File

@ -8,6 +8,6 @@ namespace Microsoft.AspNetCore.Session
{ {
public interface ISessionStore public interface ISessionStore
{ {
ISession Create(string sessionKey, TimeSpan idleTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey); ISession Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey);
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
@ -98,7 +99,7 @@ namespace Microsoft.AspNetCore.Session
} }
var feature = new SessionFeature(); var feature = new SessionFeature();
feature.Session = _sessionStore.Create(sessionKey, _options.IdleTimeout, tryEstablishSession, isNewSessionKey); feature.Session = _sessionStore.Create(sessionKey, _options.IdleTimeout, _options.IOTimeout, tryEstablishSession, isNewSessionKey);
context.Features.Set<ISessionFeature>(feature); context.Features.Set<ISessionFeature>(feature);
try try
@ -113,7 +114,7 @@ namespace Microsoft.AspNetCore.Session
{ {
try try
{ {
await feature.Session.CommitAsync(); await feature.Session.CommitAsync(context.RequestAborted);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Threading;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Session; using Microsoft.AspNetCore.Session;
@ -35,6 +36,12 @@ namespace Microsoft.AspNetCore.Builder
/// </summary> /// </summary>
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20); public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20);
/// <summary>
/// The maximim amount of time allowed to load a session from the store or to commit it back to the store.
/// Note this may only apply to asynchronous operations. This timeout can be disabled using <see cref="Timeout.InfiniteTimeSpan"/>.
/// </summary>
public TimeSpan IOTimeout { get; set; } = TimeSpan.FromMinutes(1);
#region Obsolete API #region Obsolete API
/// <summary> /// <summary>
/// <para> /// <para>

View File

@ -0,0 +1,32 @@
[
{
"TypeId": "public interface Microsoft.AspNetCore.Session.ISessionStore",
"MemberId": "Microsoft.AspNetCore.Http.ISession Create(System.String sessionKey, System.TimeSpan idleTimeout, System.Func<System.Boolean> tryEstablishSession, System.Boolean isNewSessionKey)",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Session.DistributedSession : Microsoft.AspNetCore.Http.ISession",
"MemberId": "public .ctor(Microsoft.Extensions.Caching.Distributed.IDistributedCache cache, System.String sessionKey, System.TimeSpan idleTimeout, System.Func<System.Boolean> tryEstablishSession, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, System.Boolean isNewSessionKey)",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Session.DistributedSession : Microsoft.AspNetCore.Http.ISession",
"MemberId": "public System.Threading.Tasks.Task CommitAsync()",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Session.DistributedSession : Microsoft.AspNetCore.Http.ISession",
"MemberId": "public System.Threading.Tasks.Task LoadAsync()",
"Kind": "Removal"
},
{
"TypeId": "public class Microsoft.AspNetCore.Session.DistributedSessionStore : Microsoft.AspNetCore.Session.ISessionStore",
"MemberId": "public Microsoft.AspNetCore.Http.ISession Create(System.String sessionKey, System.TimeSpan idleTimeout, System.Func<System.Boolean> tryEstablishSession, System.Boolean isNewSessionKey)",
"Kind": "Removal"
},
{
"TypeId": "public interface Microsoft.AspNetCore.Session.ISessionStore",
"MemberId": "Microsoft.AspNetCore.Http.ISession Create(System.String sessionKey, System.TimeSpan idleTimeout, System.TimeSpan ioTimeout, System.Func<System.Boolean> tryEstablishSession, System.Boolean isNewSessionKey)",
"Kind": "Addition"
}
]