Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

restore ISerializer ability, "safely" #218

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c0e9294
restore ISerializer ability, "safely"
zachrybaker Dec 18, 2023
34e33cf
make suggested changes
stanleysmall-microsoft Jan 12, 2024
4b45e2d
whitespace changes
stanleysmall-microsoft Jan 12, 2024
4661de2
update all the packages
stanleysmall-microsoft Jan 12, 2024
8f0eeec
make philo requested changes
stanleysmall-microsoft Jan 12, 2024
078989d
remove sessiondatatype from keygenerator
stanleysmall-microsoft Jan 12, 2024
a709789
update SE redis again
stanleysmall-microsoft Jan 19, 2024
e185256
add unit tests for interface and implementation
stanleysmall-microsoft Jan 25, 2024
adc6940
split ProviderConfiguration into two implementations
stanleysmall-microsoft Jan 30, 2024
6a0bd99
refactor so code is not repeated
stanleysmall-microsoft Jan 30, 2024
e1c6585
Merge branch 'Azure:main' into custom-serialization
zachrybaker Mar 11, 2024
1cd58b8
update packages
stanleysmall-microsoft Jun 5, 2024
5dba4dc
simplify code in middleware tests
stanleysmall-microsoft Jun 5, 2024
dbb3c48
update whitespace
stanleysmall-microsoft Jun 5, 2024
e46df4c
add a functional test that provides its own ISessionStateSerializer
stanleysmall-microsoft Jun 5, 2024
a53ecaf
remove changes for output cache
stanleysmall-microsoft Jun 5, 2024
2a65e87
add copyright statement to files
stanleysmall-microsoft Jun 5, 2024
835f42c
Update xunit so tests run
stanleysmall-microsoft Jun 5, 2024
f966758
Change config name to redisSerializerType
stanleysmall-microsoft Jun 5, 2024
88ca0f4
Added a serialization suffix for the serializer type
stanleysmall-microsoft Jun 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/OutputCacheProvider/RedisOutputCacheProvider.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
<Compile Include="..\Shared\*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.OutputCache.OutputCacheModuleAsync" Version="1.0.2" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNet.OutputCache.OutputCacheModuleAsync" Version="1.0.3" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="6.0.1" />
<PackageReference Include="System.IO.Pipelines" Version="6.0.2" />
<PackageReference Include="StackExchange.Redis" Version="2.7.17" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.16" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
<PackageReference Include="StackExchange.Redis" Version="2.7.17" />
</ItemGroup>

</Project>
2 changes: 0 additions & 2 deletions src/RedisSessionStateProvider/KeyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using System;

namespace Microsoft.Web.Redis
{
internal class KeyGenerator
Expand Down
12 changes: 4 additions & 8 deletions src/RedisSessionStateProvider/RedisConnectionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Web.SessionState;

namespace Microsoft.Web.Redis
Expand Down Expand Up @@ -127,11 +126,8 @@ internal byte[] SerializeSessionStateItemCollection(ISessionStateItemCollection
{
return null;
}
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms);
((SessionStateItemCollection)sessionStateItemCollection).Serialize(writer);
writer.Close();
return ms.ToArray();

return configuration.SessionDataSerializer.Serialize((SessionStateItemCollection)sessionStateItemCollection);
}

public void Set(ISessionStateItemCollection data, int sessionTimeout)
Expand All @@ -149,10 +145,10 @@ public void Set(ISessionStateItemCollection data, int sessionTimeout)
/*-------Start of Lock set operation-----------------------------------------------------------------------------------------------------------------------------------------------*/

// KEYS = { write-lock-id, data-id, internal-id }
// ARGV = { write-lock-value-that-we-want-to-set, request-timout }
// ARGV = { write-lock-value-that-we-want-to-set, request-timeout }
// lockValue = 1) (Initially) write lock value that we want to set (ARGV[1]) if we get lock successfully this will return as retArray[1]
// 2) If another write lock exists than its lock value from cache
// retArray = {lockValue , session data if lock was taken successfully, session timeout value if exists, wheather lock was taken or not}
// retArray = {lockValue , session data if lock was taken successfully, session timeout value if exists, whether lock was taken or not}
private static readonly string writeLockAndGetDataScript = (@"
local retArray = {}
local lockValue = ARGV[1]
Expand Down
10 changes: 5 additions & 5 deletions src/RedisSessionStateProvider/RedisSessionStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class RedisSessionStateProvider : SessionStateStoreProviderAsyncBase
private static object _lastException = new object();

/// <summary>
/// We do not want to throw exception from session state provider because this will break customer application and they can't get chance to handel it.
/// We do not want to throw exception from session state provider because this will break customer application and they can't get chance to handle it.
/// So if exception occurs because of some problem we store it in HttpContext using a key that we know and return null to customer. Now, when customer
/// get null from any of session operation they should call this method to identify if there was any exception and because of that got null.
/// </summary>
Expand Down Expand Up @@ -166,7 +166,7 @@ public override async Task CreateUninitializedItemAsync(HttpContextBase context,
ISessionStateItemCollection sessionData = new SessionStateItemCollection();
sessionData["SessionStateActions"] = SessionStateActions.InitializeItem;
GetAccessToStore(id);
// Converting timout from min to sec
// Converting timeout from min to sec
cache.Set(sessionData, (timeout * FROM_MIN_TO_SEC));
}
catch (Exception e)
Expand Down Expand Up @@ -257,7 +257,7 @@ private SessionStateStoreData GetItemFromSessionStore(bool isWriteLockRequired,

if (sessionData == null)
{
// If session data do not exists means it might be exipred and removed. So return null so that asp.net can call CreateUninitializedItem and start again.
// If session data do not exists means it might be expired and removed. So return null so that asp.net can call CreateUninitializedItem and start again.
// But we just locked the record so first release it
ReleaseItemExclusiveAsync(context, id, lockId, cancellationToken).Wait();
return null;
Expand Down Expand Up @@ -394,7 +394,7 @@ public override async Task SetAndReleaseItemExclusiveAsync(HttpContextBase conte
sessionItems.Remove("SessionStateActions");
}

// Converting timout from min to sec
// Converting timeout from min to sec
cache.Set(sessionItems, (item.Timeout * FROM_MIN_TO_SEC));
LogUtility.LogInfo("SetAndReleaseItemExclusive => Session Id: {0}, Session provider object: {1} => created new item in session.", id, this.GetHashCode());
} // If update if lock matches
Expand All @@ -406,7 +406,7 @@ public override async Task SetAndReleaseItemExclusiveAsync(HttpContextBase conte
{
item.Items.Remove("SessionStateActions");
}
// Converting timout from min to sec
// Converting timeout from min to sec
cache.TryUpdateAndReleaseLock(lockId, item.Items, (item.Timeout * FROM_MIN_TO_SEC));
LogUtility.LogInfo("SetAndReleaseItemExclusive => Session Id: {0}, Session provider object: {1} => updated item in session, Lock ID: {2}.", id, this.GetHashCode(), lockId);
}
Expand Down
12 changes: 6 additions & 6 deletions src/RedisSessionStateProvider/RedisSessionStateProvider.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNet.SessionState.SessionStateModule" Version="2.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="6.0.1" />
<PackageReference Include="System.IO.Pipelines" Version="6.0.2" />
<PackageReference Include="StackExchange.Redis" Version="2.7.17" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="8.0.0" />
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Threading.Channels" Version="6.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
</ItemGroup>
</Project>
39 changes: 39 additions & 0 deletions src/Shared/DefaultSessionStateSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.IO;
using System.Web.SessionState;

namespace Microsoft.Web.RedisSessionStateProvider
stanleysmall-microsoft marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Provides methods to serialize and deserialize session state data using binary serialization.
/// This is the default implementation of the ISessionDataSerializer class.
/// The implementation uses the serialization and deserialization methods provided by the SessionStateItemCollection class.
/// </summary>
internal class DefaultSessionStateSerializer : ISessionDataSerializer
{
/// <summary>
/// Deserializes the session state data.
/// </summary>
/// <param name="data">The serialized session state data as a byte array.</param>
/// <returns>The deserialized session state data.</returns>
public SessionStateItemCollection Deserialize(byte[] data)
{
MemoryStream ms = new MemoryStream(data);
BinaryReader reader = new BinaryReader(ms);
return SessionStateItemCollection.Deserialize(reader);
}

/// <summary>
/// Serializes the session state data.
/// </summary>
/// <param name="data">The session state data to serialize.</param>
/// <returns>The serialized session state data as a byte array.</returns>
public byte[] Serialize(SessionStateItemCollection data)
{
MemoryStream ms = new MemoryStream();
BinaryWriter writer = new BinaryWriter(ms);
data.Serialize(writer);
writer.Close();
return ms.ToArray();
}
}
}
21 changes: 21 additions & 0 deletions src/Shared/ISessionDataSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Web.SessionState;

/// <summary>
/// Provides methods to serialize and deserialize session state data.
/// </summary>
public interface ISessionDataSerializer
{
/// <summary>
/// Serializes the session state data.
/// </summary>
/// <param name="data">The session state data to serialize.</param>
/// <returns>The serialized session state data as a byte array.</returns>
byte[] Serialize(SessionStateItemCollection data);

/// <summary>
/// Deserializes the session state data.
/// </summary>
/// <param name="data">The serialized session state data as a byte array.</param>
/// <returns>The deserialized session state data.</returns>
SessionStateItemCollection Deserialize(byte[] data);
}
26 changes: 22 additions & 4 deletions src/Shared/ProviderConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//

using Microsoft.Web.RedisSessionStateProvider;
using System;
using System.Collections.Specialized;
using System.Configuration;
Expand All @@ -28,6 +29,7 @@ internal class ProviderConfiguration
public int ConnectionTimeoutInMilliSec { get; set; }
public int OperationTimeoutInMilliSec { get; set; }
public string ConnectionString { get; set; }
public ISessionDataSerializer SessionDataSerializer { get; set; } = new DefaultSessionStateSerializer();
stanleysmall-microsoft marked this conversation as resolved.
Show resolved Hide resolved

/* Empty constructor required for testing */

Expand Down Expand Up @@ -78,7 +80,7 @@ private ProviderConfiguration(NameValueCollection config)
{
EnableLoggingIfParametersAvailable(config);
// Get connection host, port and password.
// host, port, accessKey and ssl are firest fetched from appSettings if not found there than taken from web.config
// host, port, accessKey and ssl are first fetched from appSettings if not found there than taken from web.config
ConnectionString = GetConnectionString(config);
Host = GetStringSettings(config, "host", "127.0.0.1");
Port = GetIntSettings(config, "port", 0);
Expand Down Expand Up @@ -117,6 +119,22 @@ private ProviderConfiguration(NameValueCollection config)

ConnectionTimeoutInMilliSec = GetIntSettings(config, "connectionTimeoutInMilliseconds", 0);
OperationTimeoutInMilliSec = GetIntSettings(config, "operationTimeoutInMilliseconds", 0);

var serializationTypeNameSpace = GetStringSettings(config, "sessionSerializationNamespaceAndType", null);
var serializationTypeAssembly = GetStringSettings(config, "sessionSerializationTypeAssembly", null);

if (!string.IsNullOrEmpty(serializationTypeNameSpace) && !string.IsNullOrEmpty(serializationTypeAssembly))
{
try
{
var serializer = Activator.CreateInstance(serializationTypeAssembly, serializationTypeNameSpace);
stanleysmall-microsoft marked this conversation as resolved.
Show resolved Hide resolved
SessionDataSerializer = (ISessionDataSerializer)serializer.Unwrap();
}
catch(Exception e)
{
throw new TypeLoadException($"Could not activate Session Serialization Type from assembly {serializationTypeAssembly} and namespace {serializationTypeNameSpace}.", e);
}
}
}

// 1) Use key available inside AppSettings
Expand Down Expand Up @@ -166,7 +184,7 @@ private static string GetConnectionStringFromConfig(NameValueCollection config,
}

// 1) Check if literal value is valid integer than use it as it is
// 2) Use app setting value corrosponding to this string
// 2) Use app setting value corresponding to this string
// 3) Both are null than use default value.
private static int GetIntSettings(NameValueCollection config, string attrName, int defaultVal)
{
Expand All @@ -193,7 +211,7 @@ private static int GetIntSettings(NameValueCollection config, string attrName, i
}

// 1) Check if literal value is valid bool than use it as it is
// 2) Use app setting value corrosponding to this string
// 2) Use app setting value corresponding to this string
// 3) Both are null than use default value.
private static bool GetBoolSettings(NameValueCollection config, string attrName, bool defaultVal)
{
Expand Down Expand Up @@ -247,7 +265,7 @@ private static string GetFromConfig(NameValueCollection config, string attrName)
// Preference for fetching connection string
// Either use "settingsClassName" and "settingsMethodName" to provide connectionString
// Or use "connectionString" web.config settings to fetch value
// If using "connectionString" then it trys to do following in order
// If using "connectionString" then it tries to do following in order
// 1) Fetch value from App Settings section for key which is value for "connectionString"
// 2) If option 1 is not working, Fetch value from Web.Config ConnectionStrings section for key which is value for "connectionString"
// 3) If option 1 and 2 is not working, use value of "connectionString" as it is
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/RedisSharedConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void ForceReconnect()
var previousReconnect = lastReconnectTime;
var elapsedSinceLastReconnect = DateTimeOffset.UtcNow - previousReconnect;

// If mulitple threads call ForceReconnect at the same time, we only want to honor one of them.
// If multiple threads call ForceReconnect at the same time, we only want to honor one of them.
if (elapsedSinceLastReconnect > ReconnectFrequency)
{
lock (reconnectLock)
Expand Down
18 changes: 9 additions & 9 deletions src/Shared/StackExchangeClientConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ private object OperationExecutor(Func<object> redisOperation)
}

/// <summary>
/// If retry timout is provide than we will retry first time after 20 ms and after that every 1 sec till retry timout is expired or we get value.
/// If retry timeout is provide than we will retry first time after 20 ms and after that every 1 sec till retry timeout is expired or we get value.
/// </summary>
private object RetryLogic(Func<object> redisOperation)
{
int timeToSleepBeforeRetryInMiliseconds = 20;
int timeToSleepBeforeRetryInMilliseconds = 20;
DateTime startTime = DateTime.Now;
while (true)
{
Expand All @@ -118,15 +118,15 @@ private object RetryLogic(Func<object> redisOperation)
{
int remainingTimeout = (int)(_configuration.RetryTimeout.TotalMilliseconds - passedTime.TotalMilliseconds);
// if remaining time is less than 1 sec than wait only for that much time and than give a last try
if (remainingTimeout < timeToSleepBeforeRetryInMiliseconds)
if (remainingTimeout < timeToSleepBeforeRetryInMilliseconds)
{
timeToSleepBeforeRetryInMiliseconds = remainingTimeout;
timeToSleepBeforeRetryInMilliseconds = remainingTimeout;
}
}

// First time try after 20 msec after that try after 1 second
System.Threading.Thread.Sleep(timeToSleepBeforeRetryInMiliseconds);
timeToSleepBeforeRetryInMiliseconds = 1000;
System.Threading.Thread.Sleep(timeToSleepBeforeRetryInMilliseconds);
timeToSleepBeforeRetryInMilliseconds = 1000;
}
}
}
Expand Down Expand Up @@ -188,9 +188,9 @@ internal SessionStateItemCollection DeserializeSessionStateItemCollection(RedisR
{
try
{
MemoryStream ms = new MemoryStream((byte[])serializedSessionStateItemCollection);
BinaryReader reader = new BinaryReader(ms);
return SessionStateItemCollection.Deserialize(reader);
var bytes = (byte[])serializedSessionStateItemCollection;
stanleysmall-microsoft marked this conversation as resolved.
Show resolved Hide resolved
return _configuration.SessionDataSerializer.Deserialize(bytes);

}
catch
{
Expand Down
Loading