Skip to content

Basic support for AOT publication #629

Description

@latop2604

Hello, I’m using the Cassandra C# driver in applications published with .NET NativeAOT. At the moment, the driver crashes during connection initialization because some generic types are instantiated dynamically through reflection, and the required metadata is not preserved by AOT compilation.

The exception I’m seeing is the following:

Application: MyApp.exe
CoreCLR Version: 10.0.8
Description: The process was terminated due to an unhandled exception.
Exception Info: Cassandra.NoHostAvailableException: All hosts tried for query failed (tried 11.111.11.11:1111: NotSupportedException 'Reflection_InsufficientMetadata_EdbNeeded, System.Collections.Generic.SortedDictionary`2[System.Guid,System.Byte[]]'; 11.111.11.11:1111: NotSupportedException 'Reflection_InsufficientMetadata_EdbNeeded, System.Collections.Generic.SortedDictionary`2[System.Guid,System.Byte[]]'; ...), see Errors property for more info
   at Cassandra.Connections.Control.ControlConnection.<Connect>d__44.MoveNext() + 0x14b6
--- End of stack trace from previous location ---
   at Cassandra.Connections.Control.ControlConnection.<InitAsync>d__35.MoveNext() + 0x6e
--- End of stack trace from previous location ---
   at Cassandra.Tasks.TaskHelper.<WaitToCompleteAsync>d__10.MoveNext() + 0x18b
--- End of stack trace from previous location ---
   at Cassandra.Cluster.<Init>d__40.MoveNext() + 0x9b0
--- End of stack trace from previous location ---
   at Cassandra.Cluster.<ConnectAsync>d__50.MoveNext() + 0xfa
--- End of stack trace from previous location ---
   at Cassandra.Tasks.TaskHelper.WaitToComplete(Task, Int32) + 0xb3
   at MyAppCore.CassandraAccessor..ctor(String, String, String, String, String) + 0x11e
   at MyAppCore.DocumentAccessorFactory.CreateFromParam(String, Dictionary`2) + 0x212
   at MyApp.MainWindow.<ServerTree_SelectionChanged>g__GetOrCreateDocBrowser|5_0(ServerOptions) + 0x74
   at MyApp.MainWindow.<ServerTree_SelectionChanged>d__5.MoveNext() + 0x151
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__124_0(Object state) + 0x1a
   at Avalonia.Threading.SendOrPostCallbackDispatcherOperation.InvokeCore() + 0x19a
   at Avalonia.Threading.DispatcherOperation.Execute() + 0x4a
   at Avalonia.Threading.Dispatcher.ExecuteJob(DispatcherOperation) + 0x78
   at Avalonia.Threading.Dispatcher.ExecuteJobsCore(Boolean) + 0x387
   at Avalonia.Win32.Win32Platform.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam) + 0x58
   at MyApp!<BaseAddress>+0xfc025a

From my investigation, the issue happens when the driver dynamically creates instances of SortedDictionary<TKey, TValue> for types that were not statically referenced anywhere in the application. Since NativeAOT trims unused metadata, those generic instantiations are unavailable at runtime.

As a workaround, I was able to make the driver work by explicitly referencing the required concrete generic types in my application:

_= new Dictionary<(Type, Type), Type>
{
    [(typeof(Guid), typeof(byte[]))] = typeof(SortedDictionary<Guid, byte[]>),
    [(typeof(string), typeof(string))] = typeof(SortedDictionary<string, string>),
};

With this workaround in place, basic driver usage works correctly in NativeAOT mode:

var cluster = Cluster.Builder()/*...*/.Build();
var session = cluster.Connect(keyspace);
var cql = new SimpleStatement("SELECT * FROM system_schema.columns WHERE keyspace_name = 'foobar';");
var result = await _session.ExecuteAsync(cql).ConfigureAwait(false);
var tableColumns = result
    .GetRows()
    .Select(r => new Column
    {
        tableName = r.GetValue<string>("table_name"),
        columnName = r.GetValue<string>("column_name"),
        type = r.GetValue<string>("type"),
    })
    .GroupBy(c => c.tableName)
    .ToList();

public struct Column
{
    public string tableName { get; set; }
    public string columnName { get; set; }
    public string type { get; set; }
}

To fix it in the driver itself adding this should work:

public override IDictionary Deserialize(ushort protocolVersion, byte[] buffer, int offset, int length, IColumnInfo typeInfo)
{
    var mapInfo = (MapColumnInfo) typeInfo;
    var keyType = GetClrType(mapInfo.KeyTypeCode, mapInfo.KeyTypeInfo);
    var valueType = GetClrType(mapInfo.ValueTypeCode, mapInfo.ValueTypeInfo);
    var count = DecodeCollectionLength((ProtocolVersion)protocolVersion, buffer, ref offset);
-    var dicType = typeof(SortedDictionary<,>).MakeGenericType(keyType, valueType);
+    IDictionary result;
+
+    if (keyType == typeof(Guid) && valueType == typeof(byte[]))
+    {
+        result = new SortedDictionary<Guid, byte[]>();
+    }
+    else if(keyType == typeof(string) && valueType == typeof(string))
+    {
+        result = new SortedDictionary<string, string>();
+    }
+    else
+    {
+        var dicType = typeof(SortedDictionary<,>).MakeGenericType(keyType, valueType);
+        result = (IDictionary)Activator.CreateInstance(dicType);
+    }

    for (var i = 0; i < count; i++)

I understand that the driver relies heavily on reflection, and that full NativeAOT compatibility would likely require significant work. However, adding at least basic NativeAOT support would already be very valuable for applications targeting AOT deployment.

Thanks for considering this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions