Created
April 27, 2019 13:13
-
-
Save bartlomiejmucha/4e979d7d22b72da7358c062a245b8ec1 to your computer and use it in GitHub Desktop.
Use it to debug serialization exceptions in Sitecore. Set it as redis provider for private and shared sessions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.IO.Compression; | |
using System.Reflection; | |
using System.Runtime.Serialization; | |
using System.Runtime.Serialization.Formatters.Binary; | |
using System.Web; | |
using System.Web.SessionState; | |
using Sitecore.Diagnostics; | |
using StackExchange.Redis; | |
namespace Example | |
{ | |
public class CustomRedisProvider : Sitecore.SessionProvider.Redis.RedisSessionStateProvider | |
{ | |
private static readonly string WriteLockAndGetExpiredDataScript = string.Format("\r\n local retArray = {{}} \r\n retArray[1] = ''\r\n retArray[2] = {{}}\r\n retArray[3] = '-1'\r\n \r\n local ExpirationTime = redis.call('HGET', KEYS[2], '{1}')\r\n if ExpirationTime ~= false and ExpirationTime <= ARGV[1] then\r\n redis.call('HSET', KEYS[2], '{2}', ARGV[2])\r\n redis.call('HSET', KEYS[2], '{1}', ARGV[3])\r\n\r\n retArray[2] = redis.call('HGETALL',KEYS[1])\r\n local SessionTimeout = redis.call('HGET', KEYS[2], '{0}')\r\n if SessionTimeout ~= false then \r\n retArray[3] = SessionTimeout \r\n end\r\n end\r\n\r\n return retArray\r\n ", (object)"SessionTimeout", (object)"ExpirationTime", (object)"WriteLock"); | |
protected override string OnProcessExpiredItems(DateTime signalTime) | |
{ | |
if (signalTime > DateTime.UtcNow) | |
signalTime = DateTime.UtcNow; | |
try | |
{ | |
var cacheFieldInfo = this.GetType().BaseType.GetField("cache", BindingFlags.NonPublic | BindingFlags.Instance); | |
var cache = cacheFieldInfo.GetValue(this); | |
var methodInfo = cache.GetType().GetMethod("GetExpirationCandidateSessionIds"); | |
var methodInfo2 = cache.GetType().GetMethod("CheckAndUpdateDataExpirationTime"); | |
//var methodInfo3 = cache.GetType().GetMethod("TakeWriteLockAndGetExpiredData"); | |
var methodInfo4 = cache.GetType().GetMethod("TryRemoveAndReleaseLockIfLockIdMatch"); | |
var redisConnectionInfo = cache.GetType() | |
.GetField("redisConnection", BindingFlags.NonPublic | BindingFlags.Instance); | |
var redisConnection = redisConnectionInfo.GetValue(cache); | |
var methodInfoR1 = redisConnection.GetType().GetMethod("Eval"); | |
var methodInfoR2 = redisConnection.GetType().GetMethod("GetSessionData"); | |
var methodInfoR3 = redisConnection.GetType().GetMethod("GetSessionTimeout"); | |
var configurationFieldInfo = this.GetType().BaseType.GetField("configuration", BindingFlags.NonPublic | BindingFlags.Instance); | |
var configuration = configurationFieldInfo.GetValue(this); | |
var propertyInfo = configuration.GetType().GetProperty("PollingExpirationTimeout"); | |
var pollingExpirationTimeout = (int)propertyInfo.GetValue(configuration); | |
var propertyInfo2 = configuration.GetType().GetProperty("ApplicationName"); | |
var applicationName = (string)propertyInfo2.GetValue(configuration); | |
foreach (string candidateSessionId in (IEnumerable<string>)methodInfo.Invoke(cache, new object[] { signalTime })) | |
{ | |
DateTime utcNow = DateTime.UtcNow; | |
ISessionStateItemCollection data; | |
int sessionTimeout; | |
//var params3 = new object[] | |
//{ | |
// candidateSessionId, signalTime, utcNow, | |
// pollingExpirationTimeout, null /*data*/, null /* sessionTimeout*/ | |
//}; | |
if ((bool)methodInfo2.Invoke(cache, new object[] { candidateSessionId, signalTime }) /*&& (bool)methodInfo3.Invoke(cache, params3)*/) | |
{ | |
var sessionId = candidateSessionId; | |
var lockTime = utcNow; | |
var expirationTimeout = pollingExpirationTimeout; | |
KeyGenerator keys = KeyGenerator.GetKeys(sessionId, applicationName); | |
string str = lockTime.Ticks.ToString(); | |
DateTime dateTime = DateTime.UtcNow.AddSeconds((double)expirationTimeout); | |
string[] keyArgs = new string[2] | |
{ | |
keys.DataKey, | |
keys.InternalKey | |
}; | |
object[] valueArgs = new object[3] | |
{ | |
(object) (signalTime.Ticks / 10000L), | |
(object) str, | |
(object) (dateTime.Ticks / 10000L) | |
}; | |
var rowDataFromRedis = methodInfoR1.Invoke(redisConnection, new object[] { WriteLockAndGetExpiredDataScript, keyArgs, valueArgs }); | |
try | |
{ | |
data = (ISessionStateItemCollection)methodInfoR2.Invoke(redisConnection, new object[] { rowDataFromRedis }); | |
sessionTimeout = (int)methodInfoR3.Invoke(redisConnection, new object[] { rowDataFromRedis }); | |
//object rowDataFromRedis = this.redisConnection.Eval(RedisConnectionWrapper.WriteLockAndGetExpiredDataScript, keyArgs, valueArgs); | |
//data = this.redisConnection.GetSessionData(rowDataFromRedis); | |
//sessionTimeout = this.redisConnection.GetSessionTimeout(rowDataFromRedis); | |
if (data != null) | |
{ | |
//data = (ISessionStateItemCollection)params3[4]; | |
//sessionTimeout = (int)params3[5]; | |
SessionStateStoreData sessionStateStoreData = new SessionStateStoreData(data, new HttpStaticObjectsCollection(), sessionTimeout); | |
try | |
{ | |
this.ExecuteSessionEnd(candidateSessionId, sessionStateStoreData); | |
} | |
finally | |
{ | |
methodInfo4.Invoke(cache, | |
new object[] { candidateSessionId, (object)utcNow.Ticks.ToString() }); | |
} | |
} | |
} | |
catch (Exception e) | |
{ | |
Log.Error($"Custom Redis Provider: {e.ToString()}", this); | |
RedisResult[] redisResultArray1 = (RedisResult[])((RedisResult)rowDataFromRedis); | |
ISessionStateItemCollection stateItemCollection = (ISessionStateItemCollection)null; | |
if (redisResultArray1.Length > 1 && redisResultArray1[1] != null) | |
{ | |
RedisResult[] redisResultArray2 = (RedisResult[])redisResultArray1[1]; | |
if (redisResultArray2 != null && redisResultArray2.Length != 0 && redisResultArray2.Length % 2 == 0) | |
{ | |
if (!((string)redisResultArray2[0] == "EmptyData" && | |
(string)redisResultArray2[1] == "1")) | |
{ | |
for (int index1 = 0; index1 + 1 < redisResultArray2.Length; index1 += 2) | |
{ | |
string index2 = (string)redisResultArray2[index1]; | |
try | |
{ | |
object objectFromBytes = | |
GetObjectFromBytes((byte[])redisResultArray2[index1 + 1]); | |
} | |
catch (Exception exception) | |
{ | |
Log.Error($"Custom Redis Provider:error {exception}", this); | |
} | |
} | |
} | |
} | |
} | |
Log.Error($"Custom Redis Provider:end", this); | |
throw; | |
} | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
Log.Error("ProcessExpiredItems => {0}", (object)ex.ToString()); | |
this.LastException = ex; | |
throw; | |
} | |
return (string)null; | |
} | |
internal static object GetObjectFromBytes(byte[] dataAsBytes) | |
{ | |
if (dataAsBytes == null) | |
return (object)null; | |
BinaryFormatter binaryFormatter = new BinaryFormatter(); | |
using (MemoryStream memoryStream = new MemoryStream(dataAsBytes, 0, dataAsBytes.Length)) | |
{ | |
object obj; | |
using (BinaryReader binaryReader = new BinaryReader((Stream)memoryStream)) | |
{ | |
if (binaryReader.ReadBoolean()) | |
{ | |
using (DeflateStream deflateStream = new DeflateStream((Stream)memoryStream, CompressionMode.Decompress)) | |
{ | |
obj = Deserialize((Stream)deflateStream, binaryFormatter); | |
} | |
} | |
else | |
{ | |
obj = Deserialize((Stream)memoryStream, binaryFormatter); | |
} | |
} | |
if (obj == null || obj.GetType().Name == "RedisNull") | |
return (object)null; | |
return obj; | |
} | |
} | |
private static object Deserialize(Stream stream, BinaryFormatter binaryFormatter) | |
{ | |
var binaryParserType = typeof(System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).Assembly | |
.GetType("System.Runtime.Serialization.Formatters.Binary.__BinaryParser"); | |
var objectReaderType = typeof(System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).Assembly | |
.GetType("System.Runtime.Serialization.Formatters.Binary.ObjectReader"); | |
var internalFEType = typeof(System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).Assembly | |
.GetType("System.Runtime.Serialization.Formatters.Binary.InternalFE"); | |
var internalFEObj = Activator.CreateInstance(internalFEType); | |
internalFEType.GetField("FEtypeFormat", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj, | |
binaryFormatter.GetType().GetField("m_typeFormat", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter)); | |
internalFEType.GetField("FEserializerTypeEnum", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj, 2); | |
internalFEType.GetField("FEassemblyFormat", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj, | |
binaryFormatter.GetType().GetField("m_assemblyFormat", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter)); | |
internalFEType.GetField("FEsecurityLevel", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(internalFEObj, | |
binaryFormatter.GetType().GetField("m_securityLevel", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter)); | |
var objectReaderConstructor = objectReaderType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; | |
var objectReaderObj = objectReaderConstructor.Invoke(new object[] | |
{ | |
stream, | |
binaryFormatter.GetType().GetField("m_surrogates", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter), | |
binaryFormatter.GetType().GetField("m_context", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter), | |
internalFEObj, | |
binaryFormatter.GetType().GetField("m_binder", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter), | |
}); | |
objectReaderObj.GetType().GetField("crossAppDomainArray", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, | |
binaryFormatter.GetType().GetField("m_crossAppDomainArray", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter)); | |
objectReaderObj.GetType().GetField("bFullDeserialization", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false); | |
objectReaderObj.GetType().GetProperty("TopObject", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, (object)null); | |
objectReaderObj.GetType().GetField("topId", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, 0L); | |
objectReaderObj.GetType().GetField("bMethodCall", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false); | |
objectReaderObj.GetType().GetField("bMethodReturn", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false); | |
objectReaderObj.GetType().GetField("bIsCrossAppDomain", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, false); | |
objectReaderObj.GetType().GetField("bSimpleAssembly", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, (int)(binaryFormatter.GetType().GetField("m_assemblyFormat", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(binaryFormatter)) == 0); | |
var binaryParserConstructor = binaryParserType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0]; | |
var binaryParserObj = binaryParserConstructor.Invoke(new object[] {stream, objectReaderObj }); | |
objectReaderObj.GetType().GetField("handler", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(objectReaderObj, null); | |
binaryParserObj.GetType().GetMethod("Run", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(binaryParserObj, new object[] {}); | |
var bowmt = binaryParserObj.GetType().GetField("bowmt", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(binaryParserObj); | |
var objectString = binaryParserObj.GetType().GetField("objectString", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(binaryParserObj); | |
object name = ""; | |
if (bowmt != null) | |
{ | |
name = bowmt.GetType().GetField("name", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(bowmt); | |
} | |
object value = ""; | |
if (objectString != null) | |
{ | |
value = objectString.GetType().GetField("value", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(objectString); | |
} | |
Log.Error($"Custom Redis Provider: bowmt: {name}; objectString: {value}", binaryFormatter); | |
// NEXT STEP | |
var bFullDeserialization = objectReaderObj.GetType().GetField("bFullDeserialization", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(objectReaderObj); | |
if ((bool) bFullDeserialization == true) | |
{ | |
var objectManagerObj = objectReaderObj.GetType().GetField("m_objectManager", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(objectReaderObj); | |
var specialFixupObjectsObj = objectManagerObj.GetType() | |
.GetProperty("SpecialFixupObjects", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(objectManagerObj); | |
var fixupEnumeratorObj = specialFixupObjectsObj.GetType() | |
.GetMethod("GetFixupEnumerator", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(specialFixupObjectsObj, new Object []{}); | |
int num = -1; | |
while (num != 0) | |
{ | |
num = 0; | |
while ((bool)fixupEnumeratorObj.GetType() | |
.GetMethod("MoveNext", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(fixupEnumeratorObj, new object[0])) | |
{ | |
var currentObjectHolderObj = fixupEnumeratorObj.GetType() | |
.GetProperty("Current", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(fixupEnumeratorObj); | |
var currentObjectValueObj = currentObjectHolderObj.GetType() | |
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
if (currentObjectValueObj == null) | |
throw new SerializationException("Serialization_ObjectNotSupplied"); | |
var totalDependentObjects = currentObjectHolderObj.GetType() | |
.GetProperty("TotalDependentObjects", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
if ((int)totalDependentObjects == 0) | |
{ | |
var requiresSerInfoFixup = currentObjectHolderObj.GetType() | |
.GetProperty("RequiresSerInfoFixup", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
var isIncompleteObjectReference = (bool)currentObjectHolderObj.GetType() | |
.GetProperty("IsIncompleteObjectReference", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
if ((bool)requiresSerInfoFixup) | |
{ | |
objectManagerObj.GetType() | |
.GetMethod("FixupSpecialObject", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(objectManagerObj, new object[] { currentObjectHolderObj }); | |
++num; | |
} | |
else if (!isIncompleteObjectReference) | |
{ | |
objectManagerObj.GetType() | |
.GetMethod("CompleteObject", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(objectManagerObj, new object[] { currentObjectHolderObj, true }); | |
} | |
isIncompleteObjectReference = (bool)currentObjectHolderObj.GetType() | |
.GetProperty("IsIncompleteObjectReference", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
if (isIncompleteObjectReference) | |
{ | |
int numA = 0; | |
try | |
{ | |
object objectValueTemp; | |
object objectValueObj; | |
do | |
{ | |
objectValueTemp = currentObjectHolderObj.GetType() | |
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
var mContextObj = (StreamingContext)objectManagerObj.GetType() | |
.GetField("m_context", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(objectManagerObj); | |
var objectReference = ((IObjectReference) currentObjectValueObj); | |
var reflectedTypeObj = (TypeInfo)objectReference.GetType().GetField("m_reflectedType", | |
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(objectReference); | |
Log.Error($"Custom Redis Provider: reflected type: {reflectedTypeObj.ToString()}.", binaryFormatter); | |
var realObjectObj = objectReference.GetRealObject(mContextObj); | |
currentObjectHolderObj.GetType() | |
.GetMethod("SetObjectValue", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(currentObjectHolderObj, new object[] { realObjectObj, objectManagerObj }); | |
objectValueObj = currentObjectHolderObj.GetType() | |
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
if (objectValueObj == null) | |
{ | |
currentObjectHolderObj.GetType() | |
.GetMethod("SetObjectValue", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(currentObjectHolderObj, new object[] { objectManagerObj }); | |
objectValueObj = currentObjectHolderObj.GetType() | |
.GetProperty("ObjectValue", BindingFlags.NonPublic | BindingFlags.Instance) | |
.GetValue(currentObjectHolderObj); | |
} | |
if (numA++ == 100) | |
throw new SerializationException("Serialization_TooManyReferences"); | |
if (!(objectValueObj is IObjectReference)) | |
break; | |
} | |
while (objectValueTemp != objectValueObj); | |
} | |
catch (NullReferenceException ex) | |
{ | |
} | |
currentObjectHolderObj.GetType() | |
.GetProperty("IsIncompleteObjectReference", BindingFlags.NonPublic | BindingFlags.Instance) | |
.SetValue(currentObjectHolderObj, false); | |
objectManagerObj.GetType() | |
.GetMethod("DoNewlyRegisteredObjectFixups", BindingFlags.NonPublic | BindingFlags.Instance) | |
.Invoke(objectManagerObj, new object[] { currentObjectHolderObj }); | |
++num; | |
} | |
} | |
} | |
} | |
} | |
return null; | |
} | |
} | |
class KeyGenerator | |
{ | |
private string id; | |
public string DataKey { get; private set; } | |
public string InternalKey { get; private set; } | |
public KeyGenerator(string id, string applicationName) | |
{ | |
this.id = id; | |
this.DataKey = "{" + applicationName + "_" + id + "}_Data"; | |
this.InternalKey = "{" + applicationName + "_" + id + "}_Internal"; | |
} | |
public void RegenerateKeyStringIfIdModified(string id, string applicationName) | |
{ | |
if (id.Equals(this.id)) | |
return; | |
this.id = id; | |
this.DataKey = "{" + applicationName + "_" + id + "}_Data"; | |
this.InternalKey = "{" + applicationName + "_" + id + "}_Internal"; | |
} | |
public static string ExtractId(string key) | |
{ | |
int startIndex1 = key.IndexOf("}_", StringComparison.Ordinal); | |
int startIndex2 = key.LastIndexOf("_", startIndex1, StringComparison.Ordinal) + 1; | |
return key.Substring(startIndex2, startIndex1 - startIndex2); | |
} | |
public static KeyGenerator GetKeys(string sessionId, string applicationName) | |
{ | |
return new KeyGenerator(sessionId, applicationName); | |
} | |
public static string GetExpirationIndexKey(string applicationName) | |
{ | |
return applicationName + "_ExpirationIndex"; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment