Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

up c# syntax, make cli defaults in one style #903

Merged
merged 11 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
372 changes: 372 additions & 0 deletions .editorconfig

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion .github/workflows/csharp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,13 @@ jobs:
- name: Start redis server
run: redis-server &

- name: Format
working-directory: ./csharp
run: dotnet format --verify-no-changes --verbosity diagnostic

- name: Test
working-directory: ./csharp
run: dotnet test --framework net6.0
run: dotnet test --framework net6.0 /warnaserror

- uses: ./.github/workflows/test-benchmark
with:
Expand Down
46 changes: 24 additions & 22 deletions benchmarks/csharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
using Glide;

using CommandLine;

using Glide;

using LinqStatistics;

using StackExchange.Redis;

public static class MainClass
Expand All @@ -16,30 +20,30 @@ private enum ChosenAction { GET_NON_EXISTING, GET_EXISTING, SET };

public class CommandLineOptions
{
[Option('r', "resultsFile", Required = false, HelpText = "Set the file to which the JSON results are written.")]
public string resultsFile { get; set; } = "../results/csharp-results.json";
[Option('r', "resultsFile", Required = false, HelpText = "Set the file to which the JSON results are written.", Default = "../results/csharp-results.json")]
public string resultsFile { get; set; } = string.Empty;

[Option('d', "dataSize", Required = false, HelpText = "The size of the sent data in bytes.")]
public int dataSize { get; set; } = 100;
[Option('d', "dataSize", Required = false, HelpText = "The size of the sent data in bytes.", Default = 100)]
public int dataSize { get; set; }

[Option('c', "concurrentTasks", Required = false, HelpText = "The number of concurrent operations to perform.", Default = new[] { 1, 10, 100, 1000 })]
public IEnumerable<int> concurrentTasks { get; set; }
public IEnumerable<int> concurrentTasks { get; set; } = Enumerable.Empty<int>();

[Option('l', "clients", Required = false, HelpText = "Which clients should run")]
public string clientsToRun { get; set; } = "all";
[Option('l', "clients", Required = false, HelpText = "Which clients should run", Default = "all")]
public string clientsToRun { get; set; } = string.Empty;

[Option('h', "host", Required = false, HelpText = "What host to target")]
public string host { get; set; } = "localhost";
[Option('h', "host", Required = false, HelpText = "What host to target", Default = "localhost")]
public string host { get; set; } = string.Empty;

[Option('C', "clientCount", Required = false, HelpText = "Number of clients to run concurrently", Default = new[] { 1 })]
public IEnumerable<int> clientCount { get; set; }
public IEnumerable<int> clientCount { get; set; } = Enumerable.Empty<int>();

[Option('t', "tls", HelpText = "Should benchmark a TLS server")]
public bool tls { get; set; } = false;
[Option('t', "tls", HelpText = "Should benchmark a TLS server", Default = false)]
public bool tls { get; set; }


[Option('m', "minimal", HelpText = "Should use a minimal number of actions")]
public bool minimal { get; set; } = false;
[Option('m', "minimal", HelpText = "Should use a minimal number of actions", Default = false)]
public bool minimal { get; set; }
}

private const int PORT = 6379;
Expand Down Expand Up @@ -118,10 +122,8 @@ private static double calculate_latency(IEnumerable<double> latency_list, double

private static void print_results(string resultsFile)
{
using (FileStream createStream = File.Create(resultsFile))
{
JsonSerializer.Serialize(createStream, bench_json_results);
}
using FileStream createStream = File.Create(resultsFile);
JsonSerializer.Serialize(createStream, bench_json_results);
}

private static async Task redis_benchmark(
Expand Down Expand Up @@ -260,7 +262,7 @@ public void Dispose()
internal Func<string, Task<string?>> get;
internal Func<string, string, Task> set;

private Action disposalFunction;
private readonly Action disposalFunction;
}

private async static Task<ClientWrapper[]> createClients(int clientCount,
Expand Down Expand Up @@ -337,7 +339,7 @@ private static int number_of_iterations(int num_of_concurrent_tasks)

public static async Task Main(string[] args)
{
CommandLineOptions options = new ();
CommandLineOptions options = new();
Parser.Default
.ParseArguments<CommandLineOptions>(args).WithParsed<CommandLineOptions>(parsed => { options = parsed; });

Expand All @@ -352,4 +354,4 @@ public static async Task Main(string[] args)

print_results(options.resultsFile);
}
}
}
2 changes: 1 addition & 1 deletion benchmarks/install_and_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function runNodeBenchmark(){
function runCSharpBenchmark(){
cd ${BENCH_FOLDER}/csharp
dotnet clean
dotnet build --configuration Release
dotnet build --configuration Release /warnaserror
dotnet run --framework net6.0 --configuration Release --resultsFile=../$1 --dataSize $2 --concurrentTasks $concurrentTasks --clients $chosenClients --host $host --clientCount $clientCount $tlsFlag $portFlag $minimalFlag
}

Expand Down
162 changes: 79 additions & 83 deletions csharp/lib/AsyncClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,114 +4,110 @@

using System.Runtime.InteropServices;

namespace Glide
namespace Glide;

public class AsyncClient : IDisposable
{
public class AsyncClient : IDisposable
#region public methods
public AsyncClient(string host, UInt32 port, bool useTLS)
{
#region public methods
public AsyncClient(string host, UInt32 port, bool useTLS)
successCallbackDelegate = SuccessCallback;
var successCallbackPointer = Marshal.GetFunctionPointerForDelegate(successCallbackDelegate);
failureCallbackDelegate = FailureCallback;
var failureCallbackPointer = Marshal.GetFunctionPointerForDelegate(failureCallbackDelegate);
clientPointer = CreateClientFfi(host, port, useTLS, successCallbackPointer, failureCallbackPointer);
if (clientPointer == IntPtr.Zero)
{
successCallbackDelegate = SuccessCallback;
var successCallbackPointer = Marshal.GetFunctionPointerForDelegate(successCallbackDelegate);
failureCallbackDelegate = FailureCallback;
var failureCallbackPointer = Marshal.GetFunctionPointerForDelegate(failureCallbackDelegate);
clientPointer = CreateClientFfi(host, port, useTLS, successCallbackPointer, failureCallbackPointer);
if (clientPointer == IntPtr.Zero)
{
throw new Exception("Failed creating a client");
}
throw new Exception("Failed creating a client");
}
}

public async Task SetAsync(string key, string value)
{
var message = messageContainer.GetMessageForCall(key, value);
SetFfi(clientPointer, (ulong)message.Index, message.KeyPtr, message.ValuePtr);
await message;
}
public async Task SetAsync(string key, string value)
{
var message = messageContainer.GetMessageForCall(key, value);
SetFfi(clientPointer, (ulong)message.Index, message.KeyPtr, message.ValuePtr);
await message;
}

public async Task<string?> GetAsync(string key)
{
var message = messageContainer.GetMessageForCall(key, null);
GetFfi(clientPointer, (ulong)message.Index, message.KeyPtr);
return await message;
}
public async Task<string?> GetAsync(string key)
{
var message = messageContainer.GetMessageForCall(key, null);
GetFfi(clientPointer, (ulong)message.Index, message.KeyPtr);
return await message;
}

public void Dispose()
public void Dispose()
{
if (clientPointer == IntPtr.Zero)
{
if (clientPointer == IntPtr.Zero)
{
return;
}
messageContainer.DisposeWithError(null);
CloseClientFfi(clientPointer);
clientPointer = IntPtr.Zero;
return;
}
messageContainer.DisposeWithError(null);
CloseClientFfi(clientPointer);
clientPointer = IntPtr.Zero;
}

#endregion public methods
#endregion public methods

#region private methods
#region private methods

private void SuccessCallback(ulong index, IntPtr str)
private void SuccessCallback(ulong index, IntPtr str)
{
var result = str == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(str);
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
var result = str == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(str);
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
var message = messageContainer.GetMessage((int)index);
message.SetResult(result);
});
}
var message = messageContainer.GetMessage((int)index);
message.SetResult(result);
});
}

private void FailureCallback(ulong index)
private void FailureCallback(ulong index)
{
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
var message = messageContainer.GetMessage((int)index);
message.SetException(new Exception("Operation failed"));
});
}
var message = messageContainer.GetMessage((int)index);
message.SetException(new Exception("Operation failed"));
});
}

~AsyncClient()
{
Dispose();
}
#endregion private methods
~AsyncClient() => Dispose();
#endregion private methods

#region private fields
#region private fields

/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private FailureAction failureCallbackDelegate;
/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private readonly FailureAction failureCallbackDelegate;

/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private StringAction successCallbackDelegate;
/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private readonly StringAction successCallbackDelegate;

/// Raw pointer to the underlying native client.
private IntPtr clientPointer;
/// Raw pointer to the underlying native client.
private IntPtr clientPointer;

private readonly MessageContainer<string> messageContainer = new();
private readonly MessageContainer<string> messageContainer = new();

#endregion private fields
#endregion private fields

#region FFI function declarations
#region FFI function declarations

private delegate void StringAction(ulong index, IntPtr str);
private delegate void FailureAction(ulong index);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")]
private static extern void GetFfi(IntPtr client, ulong index, IntPtr key);
private delegate void StringAction(ulong index, IntPtr str);
private delegate void FailureAction(ulong index);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")]
private static extern void GetFfi(IntPtr client, ulong index, IntPtr key);

[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "set")]
private static extern void SetFfi(IntPtr client, ulong index, IntPtr key, IntPtr value);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "set")]
private static extern void SetFfi(IntPtr client, ulong index, IntPtr key, IntPtr value);

private delegate void IntAction(IntPtr arg);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_client")]
private static extern IntPtr CreateClientFfi(String host, UInt32 port, bool useTLS, IntPtr successCallback, IntPtr failureCallback);
private delegate void IntAction(IntPtr arg);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_client")]
private static extern IntPtr CreateClientFfi(String host, UInt32 port, bool useTLS, IntPtr successCallback, IntPtr failureCallback);

[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "close_client")]
private static extern void CloseClientFfi(IntPtr client);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "close_client")]
private static extern void CloseClientFfi(IntPtr client);

#endregion
}
}
#endregion
}
Loading
Loading