Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -579,30 +579,34 @@ private static void WriteCommandFactory(in GenerateState ctx, string baseFactory
if (additionalCommandState is not null && additionalCommandState.HasCommandProperties)
{
sb.Indent()
.NewLine().Append("var cmd = TryReuse(ref Storage, sql, commandType, args);")
.NewLine().Append("var cmd = TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool);")
.NewLine().Append("if (cmd is null)").Indent()
.NewLine().Append("cmd = base.GetCommand(connection, sql, commandType, args);");
WriteCommandProperties(ctx, sb, "cmd", additionalCommandState.CommandProperties);
sb.Outdent().NewLine().Append("return cmd;").Outdent();
}
else
{
sb.Indent(false).NewLine().Append(" => TryReuse(ref Storage, sql, commandType, args) ?? base.GetCommand(connection, sql, commandType, args);").Outdent(false);
sb.Indent(false).NewLine().Append(" => TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool) ?? base.GetCommand(connection, sql, commandType, args);").Outdent(false);
}
sb.NewLine().NewLine().Append("public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);").NewLine();
sb.NewLine().NewLine().Append("public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);").NewLine();

if (cacheCount == 1)
{
sb.Append("private static readonly DbCommandCache _cmdPool = new();").NewLine();
sb.Append("[global::System.ThreadStatic] // note this works correctly with ref").NewLine();
sb.Append("private static global::System.Data.Common.DbCommand? Storage;").NewLine();
}
else
{
sb.Append("private readonly DbCommandCache _cmdPool = new(); // note: per cache instance").NewLine();
sb.Append("protected abstract ref global::System.Data.Common.DbCommand? Storage {get;}").NewLine().NewLine();

for (int i = 0; i < cacheCount; i++)
{
sb.Append("internal sealed class Cached").Append(i).Append(" : CommandFactory").Append(index).Indent().NewLine()
.Append("protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;").NewLine()
.Append("[global::System.ThreadStatic] // note this works correctly with ref-return").NewLine()
.Append("private static global::System.Data.Common.DbCommand? s_Storage;").NewLine()
.Outdent().NewLine();
}
Expand Down
67 changes: 63 additions & 4 deletions src/Dapper.AOT/CommandFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Dapper.Internal;
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
Expand Down Expand Up @@ -175,12 +176,53 @@ protected static void SetValueWithDefaultSize(DbParameter parameter, string? val
/// <summary>
/// Provides an opportunity to recycle and reuse command instances
/// </summary>
protected static bool TryRecycle(ref DbCommand? storage, DbCommand command)
protected static bool TryRecycleInterlocked(ref DbCommand? storage, DbCommand command, DbCommandCache? cache = null)
{
// detach and recycle
command.Connection = null;
command.Transaction = null;
return Interlocked.CompareExchange(ref storage, command, null) is null;
if (Interlocked.CompareExchange(ref storage, command, null) is null)
{
return true;
}
return cache is not null && cache.TryPut(command);
}

/// <summary>
/// Provides an opportunity to recycle and reuse command instances
/// </summary>
protected static bool TryRecycleThreadStatic(ref DbCommand? storage, DbCommand command, DbCommandCache? cache = null)
{
// detach and recycle
command.Connection = null;
command.Transaction = null;
if (storage is null)
{
storage = command;
return true;
}
return cache is not null && cache.TryPut(command);
}


/// <summary>
/// A simple store for command re-use.
/// </summary>
protected sealed class DbCommandCache(int capacity = 16)
{
private readonly ConcurrentQueue<DbCommand> store = [];
internal bool TryPut(DbCommand command)
{
if (store.Count < capacity) // not exact - inherent race condition
{
store.Enqueue(command);
return true;
}
return false;
}

internal DbCommand? TryTake()
=> store.TryDequeue(out var cmd) ? cmd : null;
}
}

Expand Down Expand Up @@ -252,9 +294,26 @@ public virtual void UpdateParameters(in UnifiedCommand command, T args)
/// <summary>
/// Provides an opportunity to recycle and reuse command instances
/// </summary>
protected DbCommand? TryReuse(ref DbCommand? storage, string sql, CommandType commandType, T args)
protected DbCommand? TryReuseThreadStatic(ref DbCommand? storage, string sql, CommandType commandType, T args, DbCommandCache? cache = null)
{
var cmd = storage ?? cache?.TryTake();
storage = null;
if (cmd is not null)
{
// try to avoid any dirty detection in the setters
if (cmd.CommandText != sql) cmd.CommandText = sql;
if (cmd.CommandType != commandType) cmd.CommandType = commandType;
UpdateParameters(new(cmd), args);
}
return cmd;
}

/// <summary>
/// Provides an opportunity to recycle and reuse command instances
/// </summary>
protected DbCommand? TryReuseInterlocked(ref DbCommand? storage, string sql, CommandType commandType, T args, DbCommandCache? cache = null)
{
var cmd = Interlocked.Exchange(ref storage, null);
var cmd = Interlocked.Exchange(ref storage, null) ?? cache?.TryTake();
if (cmd is not null)
{
// try to avoid any dirty detection in the setters
Expand Down
13 changes: 9 additions & 4 deletions test/Dapper.AOT.Test/Interceptors/CacheCommand.output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,23 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje

public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
=> TryReuse(ref Storage, sql, commandType, args) ?? base.GetCommand(connection, sql, commandType, args);
=> TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool) ?? base.GetCommand(connection, sql, commandType, args);

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private readonly DbCommandCache _cmdPool = new(); // note: per cache instance
protected abstract ref global::System.Data.Common.DbCommand? Storage {get;}

internal sealed class Cached0 : CommandFactory0
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
internal sealed class Cached1 : CommandFactory0
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
Expand Down Expand Up @@ -165,9 +168,11 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje

public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
=> TryReuse(ref Storage, sql, commandType, args) ?? base.GetCommand(connection, sql, commandType, args);
=> TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool) ?? base.GetCommand(connection, sql, commandType, args);

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private static readonly DbCommandCache _cmdPool = new();
[global::System.ThreadStatic] // note this works correctly with ref
private static global::System.Data.Common.DbCommand? Storage;

}
Expand Down
13 changes: 9 additions & 4 deletions test/Dapper.AOT.Test/Interceptors/CacheCommand.output.netfx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,23 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje

public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
=> TryReuse(ref Storage, sql, commandType, args) ?? base.GetCommand(connection, sql, commandType, args);
=> TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool) ?? base.GetCommand(connection, sql, commandType, args);

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private readonly DbCommandCache _cmdPool = new(); // note: per cache instance
protected abstract ref global::System.Data.Common.DbCommand? Storage {get;}

internal sealed class Cached0 : CommandFactory0
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
internal sealed class Cached1 : CommandFactory0
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
Expand Down Expand Up @@ -165,9 +168,11 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje

public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
=> TryReuse(ref Storage, sql, commandType, args) ?? base.GetCommand(connection, sql, commandType, args);
=> TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool) ?? base.GetCommand(connection, sql, commandType, args);

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private static readonly DbCommandCache _cmdPool = new();
[global::System.ThreadStatic] // note this works correctly with ref
private static global::System.Data.Common.DbCommand? Storage;

}
Expand Down
13 changes: 9 additions & 4 deletions test/Dapper.AOT.Test/Interceptors/CommandProperties.output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
{
var cmd = TryReuse(ref Storage, sql, commandType, args);
var cmd = TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool);
if (cmd is null)
{
cmd = base.GetCommand(connection, sql, commandType, args);
Expand All @@ -212,7 +212,9 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
return cmd;
}

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private readonly DbCommandCache _cmdPool = new();
[global::System.ThreadStatic] // note this works correctly with ref
private static global::System.Data.Common.DbCommand? Storage;

}
Expand Down Expand Up @@ -256,7 +258,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
{
var cmd = TryReuse(ref Storage, sql, commandType, args);
var cmd = TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool);
if (cmd is null)
{
cmd = base.GetCommand(connection, sql, commandType, args);
Expand All @@ -269,18 +271,21 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
return cmd;
}

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private readonly DbCommandCache _cmdPool = new();
protected abstract ref global::System.Data.Common.DbCommand? Storage {get;}

internal sealed class Cached0 : CommandFactory1
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
internal sealed class Cached1 : CommandFactory1
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
{
var cmd = TryReuse(ref Storage, sql, commandType, args);
var cmd = TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool);
if (cmd is null)
{
cmd = base.GetCommand(connection, sql, commandType, args);
Expand All @@ -212,7 +212,9 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
return cmd;
}

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private readonly DbCommandCache _cmdPool = new();
[global::System.ThreadStatic] // note this works correctly with ref
private static global::System.Data.Common.DbCommand? Storage;

}
Expand Down Expand Up @@ -256,7 +258,7 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection,
string sql, global::System.Data.CommandType commandType, object? args)
{
var cmd = TryReuse(ref Storage, sql, commandType, args);
var cmd = TryReuseThreadStatic(ref Storage, sql, commandType, args, _cmdPool);
if (cmd is null)
{
cmd = base.GetCommand(connection, sql, commandType, args);
Expand All @@ -269,18 +271,21 @@ public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, obje
return cmd;
}

public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycle(ref Storage, command);
public override bool TryRecycle(global::System.Data.Common.DbCommand command) => TryRecycleThreadStatic(ref Storage, command, _cmdPool);
private readonly DbCommandCache _cmdPool = new();
protected abstract ref global::System.Data.Common.DbCommand? Storage {get;}

internal sealed class Cached0 : CommandFactory1
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
internal sealed class Cached1 : CommandFactory1
{
protected override ref global::System.Data.Common.DbCommand? Storage => ref s_Storage;
[global::System.ThreadStatic] // note this works correctly with ref-return
private static global::System.Data.Common.DbCommand? s_Storage;

}
Expand Down
Loading