diff --git a/src/Microsoft.AspNetCore.Session/DistributedSession.cs b/src/Microsoft.AspNetCore.Session/DistributedSession.cs index 6025423ad1..1f9fa7fa87 100644 --- a/src/Microsoft.AspNetCore.Session/DistributedSession.cs +++ b/src/Microsoft.AspNetCore.Session/DistributedSession.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; @@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.Session private readonly IDistributedCache _cache; private readonly string _sessionKey; private readonly TimeSpan _idleTimeout; + private readonly TimeSpan _ioTimeout; private readonly Func _tryEstablishSession; private readonly ILogger _logger; private IDictionary _store; @@ -38,6 +40,7 @@ namespace Microsoft.AspNetCore.Session IDistributedCache cache, string sessionKey, TimeSpan idleTimeout, + TimeSpan ioTimeout, Func tryEstablishSession, ILoggerFactory loggerFactory, bool isNewSessionKey) @@ -65,6 +68,7 @@ namespace Microsoft.AspNetCore.Session _cache = cache; _sessionKey = sessionKey; _idleTimeout = idleTimeout; + _ioTimeout = ioTimeout; _tryEstablishSession = tryEstablishSession; _store = new Dictionary(); _logger = loggerFactory.CreateLogger(); @@ -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. - public async Task LoadAsync() + public async Task LoadAsync(CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); if (!_loaded) { - var data = await _cache.GetAsync(_sessionKey); - if (data != null) + using (var timeout = new CancellationTokenSource(_ioTimeout)) { - Deserialize(new MemoryStream(data)); - } - else if (!_isNewSessionKey) - { - _logger.AccessingExpiredSession(_sessionKey); + var cts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken); + var data = await _cache.GetAsync(_sessionKey, cts.Token); + if (data != null) + { + Deserialize(new MemoryStream(data)); + } + else if (!_isNewSessionKey) + { + _logger.AccessingExpiredSession(_sessionKey); + } } _isAvailable = 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); - if (data == null) + try { - _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) - { - _logger.SessionCacheReadException(_sessionKey, exception); - } + + var stream = new MemoryStream(); + 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); } } diff --git a/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs b/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs index 180599ba18..49050af588 100644 --- a/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs +++ b/src/Microsoft.AspNetCore.Session/DistributedSessionStore.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Session _loggerFactory = loggerFactory; } - public ISession Create(string sessionKey, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey) + public ISession Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func tryEstablishSession, bool isNewSessionKey) { if (string.IsNullOrEmpty(sessionKey)) { @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Session throw new ArgumentNullException(nameof(tryEstablishSession)); } - return new DistributedSession(_cache, sessionKey, idleTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey); + return new DistributedSession(_cache, sessionKey, idleTimeout, ioTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/ISessionStore.cs b/src/Microsoft.AspNetCore.Session/ISessionStore.cs index 8831a60f5a..247ba2307f 100644 --- a/src/Microsoft.AspNetCore.Session/ISessionStore.cs +++ b/src/Microsoft.AspNetCore.Session/ISessionStore.cs @@ -8,6 +8,6 @@ namespace Microsoft.AspNetCore.Session { public interface ISessionStore { - ISession Create(string sessionKey, TimeSpan idleTimeout, Func tryEstablishSession, bool isNewSessionKey); + ISession Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func tryEstablishSession, bool isNewSessionKey); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs b/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs index ac234d8a68..2dbc0531c9 100644 --- a/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs +++ b/src/Microsoft.AspNetCore.Session/SessionMiddleware.cs @@ -3,6 +3,7 @@ using System; using System.Security.Cryptography; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; @@ -98,7 +99,7 @@ namespace Microsoft.AspNetCore.Session } 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(feature); try @@ -113,7 +114,7 @@ namespace Microsoft.AspNetCore.Session { try { - await feature.Session.CommitAsync(); + await feature.Session.CommitAsync(context.RequestAborted); } catch (Exception ex) { diff --git a/src/Microsoft.AspNetCore.Session/SessionOptions.cs b/src/Microsoft.AspNetCore.Session/SessionOptions.cs index 569dd5c18e..9c9856f481 100644 --- a/src/Microsoft.AspNetCore.Session/SessionOptions.cs +++ b/src/Microsoft.AspNetCore.Session/SessionOptions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Session; @@ -35,6 +36,12 @@ namespace Microsoft.AspNetCore.Builder /// public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(20); + /// + /// 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 . + /// + public TimeSpan IOTimeout { get; set; } = TimeSpan.FromMinutes(1); + #region Obsolete API /// /// diff --git a/src/Microsoft.AspNetCore.Session/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Session/breakingchanges.netcore.json new file mode 100644 index 0000000000..3b5489de5a --- /dev/null +++ b/src/Microsoft.AspNetCore.Session/breakingchanges.netcore.json @@ -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 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 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 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 tryEstablishSession, System.Boolean isNewSessionKey)", + "Kind": "Addition" + } +] \ No newline at end of file