How to store DataTable in Apache Ignite? - c#

Based on our current code implementation, we need to store System.Data.DataTable in cache. It works fine when using HttpRuntime.Cache, but not in Apache Ignite.
The following is the code snippet.
IIgnite ignite = Ignition.Start();
ICache<string, object> cache = ignite.GetOrCreateCache<string, object>("cache");
DataTable table = new DataTable();
cache.Put("1", table);
It will throw "Unable to cast object of type 'Apache.Ignite.Core.Impl.Binary.BinaryWriter' to type 'System.IConvertible'" error.
Based on the info at https://apacheignite-net.readme.io/docs/serialization,
DataTable implements ISerializable and has Serializable attribute. It should be able to serialize. I am not sure why I got this error. Any thoughts?
Environment: Ignite.NET 2.1, Visual Studio 2015

I've reproduced the issue, and I would say that this is a bug in System.Data.DataTable. Here is the code:
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
SerializationFormat remotingFormat = RemotingFormat;
bool isSingleTable = context.Context != null ? Convert.ToBoolean(context.Context, CultureInfo.InvariantCulture) : true;
SerializeDataTable(info, context, isSingleTable, remotingFormat);
}
Exception comes from
Convert.ToBoolean(context.Context, CultureInfo.InvariantCulture)
The assumption that Context can be converted to bool does not look correct, see MSDN:
additional: Any additional information to be associated with the
StreamingContext.
Ignite uses this to store BinaryWriter object for internal purposes.
Anyway, .NET framework is not going to be fixed, so I've filed an Ignite.NET bug: https://issues.apache.org/jira/browse/IGNITE-5927

Related

IndividualAssemblyLoadContext XmlSerializers.dll memory leak

I have a problem with a memory leak in .NET Core 3.1 API. The application is hosted in azure app service.
It is clearly visible on a graph that under constant load the memory is very slowly growing. it will only go down after app restart.
I created two memory dumps. One with high memory and one after restart and it's clearly visible that the reason is the app trying to load XmlSerialization.dll multiple times.
Now we have multiple other APIs that are using almost identical code when it comes to serialization and I'm not exactly sure why the problem occurs only in this one. Potentially because maybe this one has a much higher traffic when using the APIs.
I've read some articles about XmlSerializer class having memory issues but those were listed for some of the constructors we are not using. The only instance of using XmlSerializer directly in code was using an XmlSerializer(Type) constructor.
private static async Task<T> ParseResponseContentAsync<T>(HttpResponseMessage response, Accept accept)
{
try
{
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
{
using (StreamReader reader = new StreamReader(contentStream, Encoding.UTF8))
{
switch (accept)
{
case Accept.Xml:
XmlSerializer serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(reader);
case Accept.Json:
string stringContent = await reader.ReadToEndAsync();
return JsonConvert.DeserializeObject<T>(stringContent);
default:
throw new CustomHttpResponseException(HttpStatusCode.NotImplemented, $"Unsupported Accept type '{accept}'");
}
}
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Response content could not be deserialized as {accept} to {typeof(T)}", ex);
}
}
But I'm pretty sure this method is not used in this API anyway .
So another potential problematic place could be somewhere in the Controller serialization of responses.
Startup.cs registration:
services
.AddControllers(options =>
{
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(
new XmlWriterSettings
{
OmitXmlDeclaration = false
}));
options.Filters.Add<CustomHttpResponseExceptionFilter>();
})
.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(
new StringEnumConverter(typeof(CamelCaseNamingStrategy))))
.AddXmlSerializerFormatters();
Example of an endpoint:
[Produces(MimeType.ApplicationXml, MimeType.TextXml, MimeType.ApplicationJson, MimeType.TextJson)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[HttpGet("EndpointName")]
[Authorize]
public async Task<ActionResult<ResponseDto>> Get([FromModel] InputModel inputModel)
{
//some code
return responseDto;
}
Dto returned from the API:
[XmlRoot(ElementName = "SomeName")]
public class ResponseDto
{
[XmlElement(ElementName = "Result")]
public Result Result { get; set; }
[XmlAttribute(AttributeName = "Status")]
public string Status { get; set; }
[XmlAttribute(AttributeName = "DoneSoFar")]
public int DoneSoFar { get; set; }
[XmlAttribute(AttributeName = "OfTotal")]
public int OfTotal { get; set; }
}
Now I haven't been able to find any documented cases of .AddXmlSerialization causing these kinds of issues and I'm not sure what the solution or a workaround should be. Any help would be greatly appreciated.
EDIT:
I've run some additional tests as #dbc suggested.
Now it seems that we are not even hitting this line new XmlSerializer(typeof(T) in our scenarios since nothing was logged after logger code was added. We do however use default xml serialization for some of our API endpoints. Now one thing I noticed that might be causing this behavior is that the paths in memory dumps logs don't match the files that actually exist in the root folder.
The paths which are visible in memory dumps are *.Progress.Lib.XmlSerializers.dll or *.Domain.Lib.XmlSerializers.dll
Now I wonder if this isn't the issue documented here - link since I can't see those files in wwwroot directory.
If it is I'm not sure if the solution would be to somehow reference the .dlls directly ?
Edit2:
Adding a screen of how memory looks like after deploying cached serializer suggested by #dbc. There is no constant growth but it seems after few hours memory rises and doesn't go down. It is possible that the main problem is resolved but since it takes a lot of time to notice big differences we will monitor this for now. There is nothing showing in large object heap or any big number of memory is not allocated in managed memory. This API however when first deployed runs around 250 mB and after one day now at 850 mB. When we turn off the load test tool the memory didn't really go down too much.
Edit3:
So we looked closer at some historical data and it seems that the last screen is a normal behavior. It never grows beyond a certain point. Not sure why that happens but this is acceptable.
The assemblies that the new XmlSerializer(typeof(T)) constructor are trying to load are Microsoft XML Serializer Generator assemblies a.k.a Sgen.exe assemblies that might have or might not been created at the time the app was built.
But what are Sgen assemblies? In brief, XmlSerializer works by generating code to serialize and deserialize the type passed into the constructor, then compiling that generated code into a DLL and loading it into the application domain to do the actual serialization. This run-time DLL generation can be time-consuming, but as long as you use the XmlSerializer(Type) or XmlSerializer(Type, String) constructors it will only be done once per type T, with the resulting assembly being cached internally in a static dictionary by XmlSerializer.
As you might imagine this can cause the first call to new XmlSerializer(typeof(T)) to be slow, so (in .NET 2 I believe, this is all very old code) Microsoft introduced a tool to generate those run-time serialization DLLs at application build time: SGen.exe. This tool doesn't work for all types (e.g. generics) and was, if I recall correctly, finicky to use, but when it did work it did speed up serializer construction. Once loaded successfully the Sgen assembly is cached in the same cache used for generated assemblies.
And it seems like you have stumbled across a bug in .NET Core 3.1, 5, and 6 related to this:
The base class method OutputFormatter.CanWriteResult(OutputFormatterCanWriteContext context) of XmlSerializerOutputFormatter tests whether a type can be serialized by calling XmlSerializerOutputFormatter.CanWriteType(Type type). This in turn tests to see whether a type is serializable by XmlSerializer by attempting to construct a serializer for the type and returning false if construction failed because any exception was thrown. The serializer is cached if construction was successful, but nothing is cached if construction failed.
the new XmlSerializer(Type) constructor tries to load an Sgen assembly unless an assembly has already been cached for the type by a previous successful call to the constructor.
But if a type is not serializable by XmlSerializer, the constructor will throw an exception and nothing will be cached. Thus successive attempts to construct a serializer for the same non-serializable type will result in multiple calls to load Sgen assemblies.
As you yourself found, .NET Core itself permanently leaks a small amount of IndividualAssemblyLoadContext memory every time assembly load fails: Failed Assembly.Load and Assembly.LoadFile leaks memory #58093.
Putting all this together, enabling XML serialization when some of your DTOs are not serializable (because e.g. they don't have parameterless constructors) can result in ever-growing IndividualAssemblyLoadContext memory use.
So, what are your options for a workaround?
Firstly, issue #58093 was apparently fixed in .NET 7 with pull #68502 so if you upgrade to this version the problem may resolve itself.
Secondly, you could subclass XmlSerializerOutputFormatter to cache returned XmlSerializer instances even when null. This will prevent multiple attempts to create serializers for non-seializable types.
First, subclass XmlSerializerOutputFormatter and override XmlSerializerOutputFormatter.CreateSerializer(Type) as follows:
public class CachedXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
{
// Cache and reuse the serializers returned by base.CreateSerializer(t). When null is returned for a non-serializable type,
// a null serializer will be cached and returned.
static readonly ConcurrentDictionary<Type, XmlSerializer> Serializers = new ConcurrentDictionary<Type, XmlSerializer>();
public CachedXmlSerializerOutputFormatter() : base() { }
public CachedXmlSerializerOutputFormatter(ILoggerFactory loggerFactory) : base(loggerFactory) { }
public CachedXmlSerializerOutputFormatter(XmlWriterSettings writerSettings) : base(writerSettings) { }
public CachedXmlSerializerOutputFormatter(XmlWriterSettings writerSettings, ILoggerFactory loggerFactory) : base(writerSettings, loggerFactory) { }
protected override XmlSerializer CreateSerializer(Type type) { return Serializers.GetOrAdd(type, (t) => base.CreateSerializer(t)); }
}
Then replace use of XmlSerializerOutputFormatter with your subclassed version as follows:
services
.AddControllers(options =>
{
options.OutputFormatters.Add(new CachedXmlSerializerOutputFormatter (
new XmlWriterSettings
{
OmitXmlDeclaration = false
}));
options.Filters.Add<CustomHttpResponseExceptionFilter>();
})
.AddNewtonsoftJson(options => options.SerializerSettings.Converters.Add(
new StringEnumConverter(typeof(CamelCaseNamingStrategy))))
.AddXmlSerializerFormatters();
This should in theory eliminate the repeated failing calls to load Sgen assemblies.
Notes:
If you have enabled XML model binding and some of your input types are not XML-serializable, you may need to similarly subclass XmlSerializerInputFormatter. Its CreateSerializer(Type type)) also fails to cache failed attempts to construct a serializer.
Demo fiddles:
Demo fiddle showing that that multiple calls to XmlSerializerOutputFormatter.CanWriteType() for a non-serializable DTO result in multiple assembly load failures here: demo #1.
Demo fiddle showing that CachedXmlSerializerOutputFormatter fixes this problem here: demo #2.
Demo that multiple calls to XmlSerializerOutputFormatter.CanWriteType() for a serializable DTO do not result in multiple assembly load failures, and hence don't cause growing IndividualAssemblyLoadContext memory use, here: demo #3.
This might not be feasible, but could you offload the XML generation onto Azure API Management?
https://learn.microsoft.com/en-us/azure/api-management/api-management-transformation-policies#ConvertJSONtoXML

BinaryFormatter used in Interop assemblies throws UnsupportedException

Microsoft discourages the use of BinaryFormatter because it poses security problems. See: BinaryFormatter Obsoletion Strategy.
I have a .NET 6.0 WinForms code which uses the Microsoft.Office.Interop.Access.Dao interop assembly. I need it to insert an image into the Data field the Microsoft Access' system table MSysResources. This field has an Attachment Data Type. This is a multi-valued field. Using DAO is the only way of writing to this field. My (somewhat shortened) code goes like this (note: this code did work before I migrated to .NET 6.0):
using Microsoft.Office.Interop.Access.Dao;
namespace CySoft.RibbonPro.Services;
public class AccessImageResourceLoader : IAccessImageResourceLoader
{
public void UpdateImages(string accdbFile, IEnumerable<KeyValuePair<string, Image>> images)
{
var dbe = new DBEngine(); // <====== This line throws the UnsupportedException =====
Database db = dbe.OpenDatabase(accdbFile);
Recordset rs = rs = db.OpenRecordset("SELECT * FROM MSysResources WHERE 0=1", R
ecordsetTypeEnum.dbOpenDynaset, 0, LockTypeEnum.dbOptimistic);
rs.AddNew();
rs.Fields["Type"].Value = "img";
rs.Fields["Name"].Value = name;
rs.Fields["Extension"].Value = ext;
Recordset2 rsAttachment = (Recordset2)rs.Fields["Data"].Value;
rsAttachment.AddNew();
Field2 dataField = (Field2)rsAttachment.Fields["FileData"];
dataField.LoadFromFile(imageInfo.Key);
rsAttachment.Update();
rs.Update();
rs.Close();
db.Close();
}
}
The details are for illustration only. The first code line creating the DBEngine throws the exception:
BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.
The call stack is:
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.DeserializeUsingBinaryFormatter(StreamWrapper wrappedStream, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.DesigntimeLicenseContextSerializer.Deserialize(Stream o, String cryptoKey, RuntimeLicenseContext context)
at System.ComponentModel.Design.RuntimeLicenseContext.GetSavedLicenseKey(Type type, Assembly resourceAssembly)
at System.ComponentModel.LicenseManager.LicenseInteropHelper.GetCurrentContextInfo(Type type, Boolean& isDesignTime, String& key)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Internal.Runtime.InteropServices.LicenseInteropProxy.GetCurrentContextInfo(RuntimeTypeHandle rth, Boolean& isDesignTime, IntPtr& bstrKey)
at CySoft.RibbonPro.Services.AccessImageResourceLoader.UpdateImages(String accdbFile, IEnumerable`1 images) in C:\Users\Oli\Documents\Proj\CySoft\CySoft.RibbonPro\CySoft.RibbonPro\Services\AccessImageResourceLoader.cs:line 21
Where AccessImageResourceLoader.cs:line 21 is var dbe = new DBEngine();
Microsoft wants people to use another type of serialization like JSON or XML. This is not an option in this case, because I am not using it directly. It is Microsoft's own COM library which uses it.
Question:
How can I insert or update a record using Access' Attachment data type in .NET 6+?
My Attempts
I have tried to do it with System.Data.OleDb. I can read the Attachment with OleDb. But any attempt to write to this field using OleDb throws an exception.
Setting the <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization> tag in the project file does not help.
Settings the same configuration property in runtimeConfig.template.json does not help either.
I know that I could solve the problem by using Access automtation via an interop assembly. But it has the disadvantage to open the Microsoft Access application. Inserting the image through a database connection is much more elegant and did work before I migrated to .NET 6.0.
You can see here there is a switch to allow the binary serializer for the licenses file
https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContextSerializer.cs#L20
which is being read by the GetSavedLicenseKey method here
https://github.com/dotnet/runtime/blob/main/src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/Design/DesigntimeLicenseContext.cs#L84-L89
You can set this switch earlier on before initializing the DBEngine object by calling this:
AppContext.SetSwitch("System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization", true);
I haven't tried it myself but it should work.
This runtime switch might also be settable in the csproj file as described here
https://github.com/dotnet/runtime/blob/main/docs/workflow/trimming/feature-switches.md
Any feature-switch which defines property can be set in csproj file or on the command line as any other MSBuild property. Those without predefined property name the value can be set with following XML tag in csproj file.
<RuntimeHostConfigurationOption Include="<AppContext-Setting>"
Value="false"
Trim="true" />
Final words: There is even more detail on upgrading to .NET 6.0 at this blog which has another method for this flag explained.
https://www.textcontrol.com/blog/2021/12/21/migrate-a-windows-forms-desktop-application-to-dotnet-6/?hmsr=joyk.com&utm_source=joyk.com&utm_medium=referral

How to Deserialize and Serialize Build Process Parameters in TFS

I am trying to create a new build definition using TFS 2013 API. The process template that I have to refer contains several custom activities and parameters. While creating the Build Definition, some of the Property value needs to be updated dynamically. So I tried to Deserialize the process parameters using below code:
IDictionary<string, object> processParams = WorkflowHelpers.DeserializeProcessParameters(defaultTemplate.Parameters);
This code always throwing below exception:
An unhandled exception of type 'System.Xaml.XamlObjectWriterException' occurred in System.Xaml.dll
Additional information: No matching constructor found on type 'System.Activities.Activity'. You can use the Arguments or FactoryMethod directives to construct this type.
This is really frustrating and I can't get rid of this error yet.
I have also tried to deserialize the process parameters using below code:
using (StringReader stringReader = new StringReader(parameterValues))
{
object obj = XamlServices.Load(
ActivityXamlServices.CreateReader(
new XamlXmlReader((TextReader)stringReader, new XamlXmlReaderSettings { LocalAssembly = System.Reflection.Assembly.GetExecutingAssembly() }
)));
}
This works but when I serialize it again using XamlServices.Save(XamlWriter writer, object instance) method, the parameters got changed which is not compatible with build workflow.
So, how can I update the build process parameters here ? Can the WorkflowHelpers class can be used in other ways ? Or is there any other way to update the process parameters so that it gets reflected in the build definition. Any help is highly appreciated.
This is working fine now.
Below is new code:
IDictionary<string, object> processParams = WorkflowHelpers.DeserializeProcessParameters(defaultTemplate.ProcessParameters);
Instead of defaultTemplate.Parameters, I need to pass defaultTemplate.ProcessParameters

Context.CurrentMiningModel returns null in UDF

I migrated Mining Structures from a 2008 server to a 2012 server. When I try my CLR UDF (which is working fine on SQL server 2008) in a DMX query on the 2012 server, I am getting this error:
Exception has been thrown by the target of an invocation. Object reference not set to an instance of an object.
My original goal was to get the GetNodeDescription(...) method running. While debugging the problem, I could isolate the problem to this UDF which fails on my SQL server 2012
[SafeToPrepare(true)]
public static string test()
{
return Context.CurrentMiningModel.Name;
}
My guess is that CurrentMiningModel is null because the following code works fine
[SafeToPrepare(true)]
public static string testUser()
{
return Context.CurrentConnection.User.Name;
}
Any Idea on how to solve this?
Is somebody out there who can reproduce this?
Thanks.
Jan
UPDATE:
A contact at Microsoft confirmed this behaviour as desired due to a "Metadata-Refactoring" (whatever this means...). However, the website still pends to be updated appropiately.
This is not the ultimate answer but it's a workaround to get Microsoft's GetNodeDescription working (by explicitly providing the mining model):
[SafeToPrepare(true)]
public static string GetNodeDescription(string MiningModel, string nodeUniqueName)
{
if (Context.ExecuteForPrepare)
{
return string.Empty;
}
return Context.MiningModels[MiningModel].GetNodeFromUniqueName(nodeUniqueName).Descript‌​ion;
}

Clipboard behaves differently in .NET 3.5 and 4, but why?

We recently upgraded a very large project from .NET framework 3.5 to 4, and initially everything seemed to work the same. But now bugs have started to appear on copy paste operations.
I have managed to make a small reproducible app, which shows the different behavior in .NET 3.5 and 4.
I have also found a workaround (manually serialize the data to the clipboard), but I'm left with a need to know "why" there is a difference in behavior.
This is the small test app I made:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
namespace ClipboardTest
{
public class Program
{
[Serializable]
public class Element
{
public Element(string name)
{
this.name = name;
}
public string name;
}
public static List<Element> TestSerializer(List<Element> obj)
{
var memoryStream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, obj);
return (List<Element>)formatter.Deserialize(new MemoryStream(memoryStream.GetBuffer()));
}
public static List<Element> TestClipboard(List<Element> obj)
{
Clipboard.SetDataObject(obj);
return (List<Element>)Clipboard.GetDataObject().GetData(typeof(List<Element>));
}
public static void DumpObject(string testName, List<Element> obj)
{
if (obj == null)
{
Console.WriteLine("{0} : List is null", testName);
return;
}
foreach (var prop in obj)
{
Console.WriteLine("{0} : {1}", testName, prop.name);
}
}
[STAThread]
static void Main()
{
var copyData = new List<Element> { new Element("all good") };
DumpObject("Serializer", TestSerializer(copyData));
DumpObject("Clipboard", TestClipboard(copyData));
}
}
}
.NET 3.5 output:
Serializer : all good
Clipboard : all good
.NET 4 output:
Serializer : all good
Clipboard : List is null
I have looked at the .NET source for the Clipboard & DataObject class, but I couldn't see what serializer was used. The MSDN documentation says that the type must be serializable, which in this case both the List<> and Element classes are. Copying an Element object works just fine, but as soon as I copy a list of elements, it breaks.
To test, I have created 2 C# "Console Application" projects in Visual Studio 2010 SP1. The first project I have left with the default "Target framework" setting of ".NET Framework 4 Client Profile". The second project I have modified to use ".NET Framework 3.5 Client Profile".
Additional information about my Forms DLL version:
Original filename: System.Windows.Forms.dll
File version/Prouct version : 4.0.30319.235
Language: English (United States)
Date modified: 16-02-2012 22:50
I repro. You can get more insight into the bug with Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. That will stop the program when an internal exception is thrown by the clipboard code in the framework. The IDataObject.GetDataHere() implementation method fails with a COM exception, "Invalid FORMATETC structure (Exception from HRESULT: 0x80040064 (DV_E_FORMATETC))".
There is something wrong with the format. That becomes clear when you set a breakpoint after the Clipboard.SetDataObject(obj) statement. And put Clipboard.GetDataObject().GetFormats() in a debugger watch expression. I see:
"System.Collections.Generic.List`1[[ClipboardTest.Program+Element, ConsoleApplication1, Version=1.0.0.0, Culture=neutral, Public"
Note how the string is truncated, the PublicKeyToken part got mangled. You can arbitrarily alter this truncated string by changing the namespace name and the project name. Make them short enough and the program won't fail.
Clearly this is the cause of the problem. The string length is clipped to 127 characters, any type whose full name is longer than that is going to cause this failure. With a high likelihood that this will be a generic type since they have very long names.
Please report this bug at connect.microsoft.com. Your code demonstrates the bug very well, just posting a link to it in your bug report will be sufficient. I don't have a very good workaround, ensuring the name is short enough is not very practical. But you can with code like this:
// Put it on the clipboard, use a wrapper type with a short name
var envelope = new List<object>();
envelope.AddRange(obj);
Clipboard.SetDataObject(envelope);
// Retrieve from clipboard, unwrap back to original type
envelope = (List<object>)Clipboard.GetDataObject().GetData(typeof(List<object>));
var retval = new List<Element>();
retval.AddRange(envelope.Cast<Element>());
return retval;
UPDATE: this bug is reported fixed in VS2013.

Categories