A reference-tracked object changed reference during deserialization - c#

This is the code to replicate the issue. Basically it fails on deserialization, with error "A reference-tracked object changed reference during deserialization". It's kind of interesting, if I remove the following line from IExecEnv setup:
metaType.AsReferenceDefault = true;
then the test passes. But this sounds odd. Is it a limitation of Protobuf-net?
using ProtoBuf;
using ProtoBuf.Meta;
namespace QRM.Analytics.Serialization.Commit.Tests;
public class UnitTest1
{
private interface IExecEnv
{
int GetId();
}
[Immutable]
private class ExecEnv : IExecEnv
{
public static readonly ExecEnv DefaultEnv = new ExecEnv(1);
private readonly int _id;
private ExecEnv(int id)
{
_id = id;
}
public int GetId()
{
return _id;
}
}
[Serializable]
private sealed class ExecEnvSurrogate
{
[ProtoConverter]
public static IExecEnv FromSurrogate(ExecEnvSurrogate surrogate)
{
if (surrogate == null)
return null;
return ExecEnv.DefaultEnv;
}
[ProtoConverter]
public static ExecEnvSurrogate ToSurrogate(IExecEnv env)
{
if (env == null)
return null;
return new ExecEnvSurrogate();
}
}
private class WithExecEnv
{
private IExecEnv _env;
public WithExecEnv(IExecEnv env)
{
_env = env;
}
public IExecEnv Env => _env;
}
private void SetupModel()
{
var metaType = RuntimeTypeModel.Default.Add(typeof(IExecEnv), false);
metaType.AsReferenceDefault = true;
var metaType3 = RuntimeTypeModel.Default.Add(typeof(WithExecEnv), false);
metaType3.UseConstructor = false;
metaType3.AddField(1, "_env");
var metaType4 = RuntimeTypeModel.Default.Add(typeof(ExecEnvSurrogate), false);
metaType4.UseConstructor = false;
metaType.SetSurrogate(typeof(ExecEnvSurrogate));
}
[Test]
public void CloneExecEnvWithSurrogate()
{
SetupModel();
var withExecEnv = new WithExecEnv(ExecEnv.DefaultEnv);
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, withExecEnv);
stream.Seek(0, SeekOrigin.Begin);
var clone = Serializer.Deserialize<WithExecEnv>(stream);
Assert.NotNull(clone);
Assert.Equal(withExecEnv.Env.GetId(), clone.Env.GetId());
}
}
}

Yes, reference-tracking is ... a massive PITA; it works in some limited scenarios, but there are situations where it becomes unreliable. It also isn't part of the core protobuf specification, but is hacked on top at the library level. For these reasons, it is not recommended, and is deprecated going forward; it cannot be made reliable, and I'd rather not encourage people to get into dangerous territory.

Related

How to do .NET runtime method patch on generic class's static non-generic method? (Harmony or MonoMod)

In this example, I want to patch PatchTarget.QSingleton\<T\>.get_Instance().
How to get it done with Harmony or MonoMod?
Harmony:
"Unhandled exception. System.NotSupportedException: Specified method
is not supported."
MonoMod:
"Unhandled exception. System.ArgumentException: The given generic
instantiation was invalid."
Code snippet: (runnable with dotnetfiddle.net)
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.Write($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.Write($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
Patch.HarmonyPatch.init();
// Patch.MonoModPatch.init();
Console.WriteLine($"done");
}
}
After some trial and error, I got something working, but not the reason behind it.
Both Harmony and MonoMod.RuntimeDetour can hook with the typeof(QSingleton<SampleA>).GetMethod(), but not typeof(QSingleton<>).GetMethod().
Harmony output is unexpected.
Harmony attribute annotation doesn't seem to work.
Generating IL seems useless due to the potential lack of TypeSpec for generic.
Questions:
What is the difference between QSingleton<>.Instance and QSingleton<SampleA>.Instance in the sample?
I would guess that <>.Instance is MethodDef, while <SampleA>.Instance is TypeSpec.MemberRef.
Why does Harmony/MonoMod.RuntimeDetour need TypeSpec.MemberRef? For generating redirection stub?
Is it possible to fix the hook under Harmony?
Can Harmony/MonoMod generates "ldtoken <TypeSpec>" if TypeSpec already exists?
Can Harmony/MonoMod dynamically generates necessary TypeSpec for generics?
Code snippet: (runnable with dotnetfiddle.net)
using System;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.WriteLine($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
public class SampleA : QSingleton<SampleA> {
public SampleA() { Console.WriteLine("SampleA ctor"); }
}
public class SampleB : QSingleton<SampleB> {
public SampleB() { Console.WriteLine("SampleB ctor"); }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
// For Harmony as Prefix, but attribute does not work.
public static bool InstanceHackPrefix(T __result) {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
__result = null;
return false;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
// Attribute does not work.
// Transpiler does not work because the lack of TypeSpec to setup generic parameters.
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHackPrefix");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
_ = PatchTarget.SampleA.Instance;
// MonoMod works (replaces globally).
// Harmony hooks, but in an expected way (T becomes SampleB, not 1st generic type parameter).
// try { Patch.HarmonyPatch.init(); } catch (Exception e) { Console.WriteLine($"Harmony error: {e.ToString()}"); }
try { Patch.MonoModPatch.init(); } catch (Exception e) { Console.WriteLine($"MonoMod error: {e.ToString()}"); }
_ = PatchTarget.SampleB.Instance;
_ = PatchTarget.SampleA.Instance;
Console.WriteLine($"done");
}
}
MonoMod.RuntimeDetour Output:(Work as intended)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleA.Instance: impl=InstanceHack
Harmony Output:(Broken <T>)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleB.Instance: impl=InstanceHack

Xml output class to generic class

I have a class I found on another post that I'm trying to modify.
using System;
using System.IO;
namespace Misc
{
internal class ConfigManager
{
private string _sConfigFileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}.xml", AppDomain.CurrentDomain.FriendlyName));
private Config m_oConfig = new Config();
public Config MyConfig
{
get { return m_oConfig; }
set { m_oConfig = value; }
}
// Load configuration file
public void LoadConfig()
{
if (System.IO.File.Exists(_sConfigFileName))
{
System.IO.StreamReader srReader = System.IO.File.OpenText(_sConfigFileName);
Type tType = m_oConfig.GetType();
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
object oData = xsSerializer.Deserialize(srReader);
m_oConfig = (Config)oData;
srReader.Close();
}
}
// Save configuration file
public void SaveConfig()
{
System.IO.StreamWriter swWriter = System.IO.File.CreateText(_sConfigFileName);
Type tType = m_oConfig.GetType();
if (tType.IsSerializable)
{
System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
xsSerializer.Serialize(swWriter, m_oConfig);
swWriter.Close();
}
}
}
}
I'd like to pass in an object of type X and have it save. On that same premise, I'd like to pass in a type and have it pass back the object of type X. Right now, it is hard coded to use Config. So, if there is a way to pass in the class object (?) then I'd like it to save it as that object and/or return it of that object.
Is that possible? If so, how would I go about doing this?
Use generic:
internal class ConfigManager<T>
{
private string _fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}.xml", AppDomain.CurrentDomain.FriendlyName));
private T _config;
private XmlSerializer serializer = new XmlSerializer(typeof(T));
public T MyConfig
{
get { return _config; }
set { _config = value; }
}
public void LoadConfig()
{
if (File.Exists(_fileName))
{
using (var reader = File.OpenText(_fileName))
{
_config = (T)serializer.Deserialize(reader);
}
}
}
public void SaveConfig()
{
using (var writer = File.CreateText(_fileName))
{
serializer.Serialize(writer, _config);
}
}
}
Usage:
var man = new ConfigManager<Foo>();
Basically, we'd need to make it work with Generics. So, we start by giving it a type variable, and replace every use of the class Config with T:
internal class Manager<T>
{
private T m_oObj; // etc
Next, I'll just do one method; you can do the rest. (and I'm removed the explicit namespaces, cuz they're ugly)
using System.IO;
using System.Xml.Serialization;
public void LoadConfig<T>()
{
if (File.Exists(_sConfigFileName))
{
var srReader = File.OpenText(_sConfigFileName);
var xsSerializer = new XmlSerializer(typeof(T));
var oData = xsSerializer.Deserialize(srReader);
m_oObj = (T)oData;
srReader.Close();
}
}

HttpSessionStateBase losing property values of inherited type

We are using HttpSessionStateBase to store messages in a set up similar to this working example:
public class HttpSessionMessageDisplayFetch : IMessageDisplayFetch
{
protected HttpSessionStateBase _session;
private IList<ICoreMessage> messages
{
get
{
if (_session[EchoCoreConstants.MESSAGE_KEY] == null)
_session[EchoCoreConstants.MESSAGE_KEY] = new List<ICoreMessage>();
return _session[EchoCoreConstants.MESSAGE_KEY] as IList<ICoreMessage>;
}
}
public HttpSessionMessageDisplayFetch()
{
if (HttpContext.Current != null)
_session = new HttpSessionStateWrapper(HttpContext.Current.Session);
}
public void AddMessage(ICoreMessage message)
{
if (message != null)
messages.Add(message);
}
public IEnumerable<IResultPresentation> FlushMessagesAsPresentations(IResultFormatter formatter)
{
var mToReturn = messages.Select(m => m.GetPresentation(formatter)).ToList();
messages.Clear();
return mToReturn;
}
}
When we pass in a QualityExplicitlySetMessage (which inherits from ICoreMessage, see below) it is saved correctly to messages.
This is how the object looks after being inserted into the messages list, at the end of AddMessage(ICoreMessage message) above.
But when we come to access it after changing controllers the inherited member's properties are null, which causes a variety of null reference exceptions.
This is how the object now looks after we call FlushMessagesAsPresentations. I've commented out var mToReturn... as this tries to access one of these null ref properties.
I'd like to ask the following:
Why is the HttpSessionStateBase failing to capture these values taken
by the inherited type?
Is this an issue in saving to the HttpSession or in retrieving?
Is this anything to do with, as I suspect, inheritance?
Or is the fact I'm potentially calling a new controller that dependency injects the HttpSessionMessageDisplayFetch causing an issue?
I'm a first-time poster so please let me know if I'm making any kind of faux pas - Super keen to learn! Any input is very welcome.
Some potentially useful code snippets:
QualityExplicitlySetMessage
public class QualityExplicitlySetMessage : QualityChangeMessage
{
public QualityExplicitlySetMessage(IQPossession before, IQPossession after, IQEffect qEffect)
: base(before, after, qEffect)
{
IsSetToExactly = true;
}
}
QualityChangeMessage - Working example
public abstract class QualityChangeMessage : CoreMessage, IQualityChangeMessage
{
protected PossessionChange Change;
public PossessionChange GetPossessionChange()
{
return Change;
}
protected QualityChangeMessage(IQPossession before, IQPossession after, IQEffect qEffect)
{
Change = new PossessionChange(before, after, qEffect);
StoreQualityInfo(qEffect.AssociatedQuality);
}
public override IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetQualityResult(this);
}
#region IQualityChangeMessage implementation
public int LevelBefore
{
get { return Change.Before.Level; }
}
//... And so on with values dependent on the Change property.
}
CoreMessage - Working example
public abstract class CoreMessage : ICoreMessage
{
public string MessageType
{
get { return GetType().ToString(); }
}
public string ImageTooltip
{
get { return _imagetooltip; }
set { _imagetooltip = value; }
}
public string Image
{
get { return _image; }
set { _image = value; }
}
public int? RelevantQualityId { get; set; }
protected void StoreQualityInfo(Quality q)
{
PyramidNumberIncreaseLimit = q.PyramidNumberIncreaseLimit;
RelevantQualityId = q.Id;
RelevantQualityName = q.Name;
ImageTooltip = "<strong>" + q.Name + "</strong><br/>" + q.Description + "<br>" +
q.EnhancementsDescription;
Image = q.Image;
}
public virtual IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetResult(this);
}
}
UserController - Working example.
public partial class UserController : Controller
{
private readonly IMessageDisplayFetch _messageDisplayFetch;
public UserController(IMessageDisplayFetch messageDisplayFetch)
{
_messageDisplayFetch = messageDisplayFetch;
}
public virtual ActionResult MessagesForStoryletWindow()
{
var activeChar = _us.CurrentCharacter();
IEnumerable<IResultPresentation> messages;
messages = _messageDisplayFetch.FlushMessagesAsPresentations(_storyFormatter);
var vd = new MessagesViewData(messages)
{
Character = new CharacterViewData(activeChar),
};
return View(Views.Messages, vd);
}
}

AppDomainUnloadedException using MAF (Microsoft Addin Framework)

I'm having a AppDomainUnloadedExcpetion from time to time on some machines when calling an addin hosted using MAF (System.AddIn). This is what my pipeline looks like:
[AddInContract]
public interface IGatewayV2 : IContract
{
ITopUpResultContract TopUpPhoneAccount(ITopUpRequestContract request);
}
This is my Host Adapter:
[HostAdapterAttribute()]
public class GatewayContractToViewHostSideAdapter : Dogs.Pipeline.HostView.IGatewayV2
{
private Dogs.Pipeline.Contracts.IGatewayV2 _contract;
private ContractHandle _handle;
public GatewayContractToViewHostSideAdapter(Dogs.Pipeline.Contracts.IGatewayV2 contract)
{
_contract = contract;
_handle = new ContractHandle(contract);
}
public TopUpResult TopUpPhoneAccount(TopUpRequest request)
{
var topupRequestViewToContractHostSideAdapter = new TopUpRequestViewToContractHostSideAdapter(request);
return
new TopUpResultContractToViewHostSideAdapter(
_contract.TopUpPhoneAccount(topupRequestViewToContractHostSideAdapter));
}
}
always on the host adapter side I have a topupRequestViewToContractHostSideAdapter:
class TopUpRequestViewToContractHostSideAdapter : ContractBase, ITopUpRequestContract
{
private TopUpRequest _topUpRequest;
public TopUpRequestViewToContractHostSideAdapter(TopUpRequest topUpRequest)
{
this._topUpRequest = topUpRequest;
}
//properties here
)
and a TopUpResultContractToViewHostSideAdapter which handles the reference to the external appdomain:
public class TopUpResultContractToViewHostSideAdapter : TopUpResult
{
private ITopUpResultContract _contract;
private ContractHandle _handle;
public TopUpResultContractToViewHostSideAdapter(ITopUpResultContract contract)
{
this._contract = contract;
_handle = new ContractHandle(contract);
}
//properties here
}
On the addin side instead I have the following code:
[AddInAdapter()]
public class GatewayViewToContractAddInSideAdapter : ContractBase, Dogs.Pipeline.Contracts.IGatewayV2
{
private Dogs.Pipeline.AddinViewV2.IGatewayV2 _view;
public GatewayViewToContractAddInSideAdapter(Dogs.Pipeline.AddinViewV2.IGatewayV2 view)
{
this._view = view;
}
public ITopUpResultContract TopUpPhoneAccount(ITopUpRequestContract request)
{
var result = _view.TopUpPhoneAccount(new TopUpRequestContractToViewAdapter(request));
return new TopUpResultViewToContractAdapter(result);
}
}
where the TopUpResultViewToContractAdapter is:
public class TopUpResultViewToContractAdapter : ContractBase, ITopUpResultContract
{
private TopUpResult _topUpResult;
public TopUpResultViewToContractAdapter(TopUpResult result)
{
this._topUpResult = result;
}
//properties here
}
and the TopUpRequestContractToViewAdapter is:
public class TopUpRequestContractToViewAdapter : TopUpRequest
{
private ITopUpRequestContract _contract;
private ContractHandle _handle;
public TopUpRequestContractToViewAdapter(ITopUpRequestContract contract)
{
_contract = contract;
_handle = new ContractHandle(contract);
}
//properties here
}
Everything seems compliant with what I've read so far and in particular with what I can read at this link: http://msdn.microsoft.com/en-us/library/bb384186(v=vs.100).aspx
So I'm wondering if I'm doing something wrong or there is a possibility that the pipeline doesn't work on certain machines (as I'm having problem not on all the machines I'm using for testing).
Thanks for any hint.

Sourcing AppSettings from database & cache

At my office, it has been deemed that we are to put our AppSettings for the Web.Config in the database. As such, I created the following, but have some doubts about a couple aspects of the code.
So my question is:
The line containing "Cache cache = new Cache()" in the UTILITY class is probably wrong because it creates a NEW cache object.
Q: So, what should I be doing for that line?
...any help is appreciated.
The overall objective was to be able to make a call like this:
Utility.GetConfigurationValue(ConfigurationSection.AppSettings, "myVariable");
...and have it retrieve from the cache or the database auto-magically.
THE UTILITY CODE:
public static class Utility
{
#region "Configurations"
public static String GetConfigurationValue(ConfigurationSection section, String key)
{
Configurations config = new Configurations();
Cache cache = new Cache(); // <--- This is probably wrong!!!!
if (!cache.TryGetItemFromCache<Configurations>(out config))
{
config.List(SNCLavalin.US.Common.Enumerations.ConfigurationSection.AppSettings);
cache.AddToCache<Configurations>(config, DateTime.Now.AddMinutes(15));
}
var result = (from record in config
where record.Key == key
select record).FirstOrDefault();
return (result == null) ? null : result.Value;
}
#endregion
}
THE EXTENSION CODE:
public static class Extensions
{
#region "System.Web.Caching"
public static void Remove<T>(this Cache cache) where T : class
{
cache.Remove(typeof(T).Name);
}
public static void AddToCache<T>(this Cache cache, object item, DateTime absoluteExpiration) where T : class
{
T outItem = null;
if (cache.TryGetItemFromCache<T>(out outItem))
return;
cache.Insert(typeof(T).Name,
item,
null,
absoluteExpiration,
System.Web.Caching.Cache.NoSlidingExpiration,
System.Web.Caching.CacheItemPriority.Normal,
null);
}
public static bool TryGetItemFromCache<T>(this Cache cache, out T item) where T : class
{
item = cache.Get(typeof(T).Name) as T;
return item != null;
}
#endregion
}
THE LIST-CLASS CODE:
public class Configurations : List<Configuration>
{
#region CONSTRUCTORS
public Configurations() : base()
{
initialize();
}
public Configurations(int capacity) : base(capacity)
{
initialize();
}
public Configurations(IEnumerable<Configuration> collection) : base(collection)
{
initialize();
}
#endregion
#region PROPERTIES & FIELDS
private Crud _crud;
#endregion
#region EVENTS
#endregion
#region METHODS
private void initialize()
{
_crud = new Crud("CurrentDbConnection");
}
public Configurations List(ConfigurationSection section)
{
using (DbCommand dbCommand = _crud.Db.GetStoredProcCommand("spa_LIST_SecConfiguration"))
{
_crud.Db.AddInParameter(dbCommand, "#Section", DbType.String, section.ToString());
_crud.List(dbCommand, PopulateFrom);
}
return this;
}
public void PopulateFrom(DataTable table)
{
this.Clear();
foreach (DataRow row in table.Rows)
{
Configuration instance = new Configuration();
instance.PopulateFrom(row);
this.Add(instance);
}
}
#endregion
}
THE ITEM-CLASS CODE:
public class Configuration
{
#region CONSTRUCTORS
public Configuration()
{
initialize();
}
#endregion
#region PROPERTIES & FIELDS
private Crud _crud;
public string Section { get; set; }
public string Key { get; set; }
public string Value { get; set; }
#endregion
#region EVENTS
#endregion
#region METHODS
private void initialize()
{
_crud = new Crud("CurrentDbConnection");
Clear();
}
public void Clear()
{
this.Section = "";
this.Key = "";
this.Value = "";
}
public void PopulateFrom(DataRow row)
{
Clear();
this.Section = row["Section"].ToString();
this.Key = row["Key"].ToString();
this.Value = row["Value"].ToString();
}
#endregion
}
You have correctly identified the problem - the current code creates a new Cache instance on each call to GetConfigurationValue which defeats the purpose of caching. You need to make the Cache instance static rather than creating a new instance each time.
public static class Utility
{
private static Cache cache = new Cache(); // Static class variable
#region "Configurations"
public static String GetConfigurationValue(ConfigurationSection section, String key)
{
Configurations config = new Configurations();
// Cache cache = new Cache(); --- removed
...
}
}
ADDENDUM TO ANSWER:
I did (in fact) need to point the cache variable to something else. It wouldn't work until I did the following in the Utility class.
private static Cache cache = System.Web.HttpRuntime.Cache;

Categories