From 12b0e1391d933cedc5da86b5f3abc571f6779910 Mon Sep 17 00:00:00 2001 From: Guriy Samarin Date: Thu, 1 Feb 2024 19:12:03 +0000 Subject: [PATCH 1/7] add instruction to run c# and modern .net version --- .vscode/launch.json | 43 +++++------- benchmarks/csharp/Program.cs | 4 +- benchmarks/csharp/csharp_benchmark.csproj | 2 +- csharp/DEVELOPER.md | 81 +++++++++++++++++++++++ csharp/README.md | 42 +++++++++++- csharp/csharp.sln | 6 ++ csharp/lib/glide.csproj | 18 +++-- csharp/tests/tests.csproj | 2 +- 8 files changed, 158 insertions(+), 40 deletions(-) create mode 100644 csharp/DEVELOPER.md diff --git a/.vscode/launch.json b/.vscode/launch.json index d13870c92e..9962d2785a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,42 +1,29 @@ { "version": "0.2.0", "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/csharp/tests/bin/Debug/net6.0/tests.dll", - "args": [], - "cwd": "${workspaceFolder}/csharp/tests", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - }, { "name": "C# benchmark Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/benchmarks/csharp/bin/Debug/net6.0/csharp_benchmark.dll", - "args": [], + "program": "${workspaceFolder}/benchmarks/csharp/bin/Debug/net8.0/csharp_benchmark.dll", + "args": [ + "--dataSize", + "1024", + "--resultsFile", + "test.json", + "--concurrentTasks", + "4", + "--clients", + "all", + "--host", + "localhost", + "--clientCount", + "4" + ], "cwd": "${workspaceFolder}/benchmarks/csharp", "console": "internalConsole", "stopAtEntry": true - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" } ] } diff --git a/benchmarks/csharp/Program.cs b/benchmarks/csharp/Program.cs index 48a4671db3..880237ed0f 100644 --- a/benchmarks/csharp/Program.cs +++ b/benchmarks/csharp/Program.cs @@ -4,9 +4,7 @@ using System.Collections.Concurrent; using System.Diagnostics; -using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; using Glide; using CommandLine; using LinqStatistics; @@ -339,7 +337,7 @@ private static int number_of_iterations(int num_of_concurrent_tasks) public static async Task Main(string[] args) { - CommandLineOptions options = new CommandLineOptions(); + CommandLineOptions options = new (); Parser.Default .ParseArguments(args).WithParsed(parsed => { options = parsed; }); diff --git a/benchmarks/csharp/csharp_benchmark.csproj b/benchmarks/csharp/csharp_benchmark.csproj index 2d8868d9df..de05de0d82 100644 --- a/benchmarks/csharp/csharp_benchmark.csproj +++ b/benchmarks/csharp/csharp_benchmark.csproj @@ -12,7 +12,7 @@ Exe - net6.0 + net6.0;net8.0 enable enable diff --git a/csharp/DEVELOPER.md b/csharp/DEVELOPER.md new file mode 100644 index 0000000000..cbc0d9b060 --- /dev/null +++ b/csharp/DEVELOPER.md @@ -0,0 +1,81 @@ +# Developer Guide + +This document describes how to set up your development environment to build and test the GLIDE for Redis C# wrapper. + +### Development Overview + +The GLIDE C# wrapper consists of both C# and Rust code. + +### Build from source + +#### Prerequisites + +Software Dependencies + +- .net sdk 6 or 8 +- git +- GCC +- pkg-config +- protoc (protobuf compiler) +- openssl +- openssl-dev +- rustup + +**Dependencies installation for MacOS** + +visit https://dotnet.microsoft.com/en-us/download/dotnet +to download .net 6 and 8 installer +```bash +brew update +brew install git gcc pkgconfig protobuf openssl +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source "$HOME/.cargo/env" +``` + +#### Building and installation steps + +Before starting this step, make sure you've installed all software requirments. + +1. Clone the repository: +```bash +VERSION=0.1.0 # You can modify this to other released version or set it to "main" to get the unstable branch +git clone --branch ${VERSION} https://github.com/aws/glide-for-redis.git +cd glide-for-redis +``` +2. Initialize git submodule: +```bash +git submodule update --init --recursive +``` +3. Build the c# wrapper: + Choose a build option from the following and run it from the `csharp` folder: + + 1. Build in release mode, stripped from all debug symbols: + + ```bash + dotnet build + ``` + + 2. Build with debug symbols: + + ```bash + dotnet build --debug + ``` + +4. Run tests: + + 1. Ensure that you have installed redis-server and redis-cli on your host. You can find the Redis installation guide at the following link: [Redis Installation Guide](https://redis.io/docs/install/install-redis/install-redis-on-linux/). + + 2. Execute the following command from the root project folder: + ```bash + docker run --name some-redis -d redis -p 6379:6379 + cd benchmarks/csharp + dotnet run --framework net8.0 --dataSize 1024 --resultsFile test.json --concurrentTasks 4 --clients all --host localhost --clientCount 4 + ``` + +### Submodules + +After pulling new changes, ensure that you update the submodules by running the following command: + +```bash +git submodule update +``` diff --git a/csharp/README.md b/csharp/README.md index 1d3b34df64..3f88f28591 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -2,6 +2,44 @@ The C# wrapper is currently not in a usable state. -## Recommended VSCode extensions +# GLIDE for Redis -[C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) - lightweight language server and in-editor test runner. +General Language Independent Driver for the Enterprise (GLIDE) for Redis, is an AWS-sponsored, open-source Redis client. GLIDE for Redis works with any Redis distribution that adheres to the Redis Serialization Protocol (RESP) specification, including open-source Redis, Amazon ElastiCache for Redis, and Amazon MemoryDB for Redis. +Strategic, mission-critical Redis-based applications have requirements for security, optimized performance, minimal downtime, and observability. GLIDE for Redis is designed to provide a client experience that helps meet these objectives. It is sponsored and supported by AWS, and comes pre-configured with best practices learned from over a decade of operating Redis-compatible services used by hundreds of thousands of customers. To help ensure consistency in development and operations, GLIDE for Redis is implemented using a core driver framework, written in Rust, with extensions made available for each supported programming language. This design ensures that updates easily propagate to each language and reduces overall complexity. + +## Supported Redis Versions + +GLIDE for Redis is API-compatible with open source Redis version 6 and 7. + +## Current Status + +We've made GLIDE for Redis an open-source project, and are releasing it in Preview to the community to gather feedback, and actively collaborate on the project roadmap. We welcome questions and contributions from all Redis stakeholders. +This preview release is recommended for testing purposes only. + +# Getting Started - C# Wrapper + +## System Requirements + +The beta release of GLIDE for Redis was tested on Intel x86_64 using Ubuntu 22.04.1, Amazon Linux 2023 (AL2023), and macOS 12.7. + +## .net sdk supported version + +.net 6.0 or higher. + +## Installation and Setup + +## Basic Example + +```csharp + +using Glide; + +AsyncClient glideClient = new (host, PORT, useTLS); +await glideClient.SetAsync("foo", "bar"); +string? value = await glideClient.GetAsync("foo"); +glideClient.Dispose(); +``` + +### Building & Testing + +Development instructions for local building & testing the package are in the [DEVELOPER.md](https://github.com/aws/glide-for-redis/blob/main/csharp/DEVELOPER.md#build-from-source) file. diff --git a/csharp/csharp.sln b/csharp/csharp.sln index 8458290dfe..fc86e94aec 100644 --- a/csharp/csharp.sln +++ b/csharp/csharp.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "glide", "lib\glide.csproj", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{B5A73329-1C34-4D33-8013-D030983A59FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp_benchmark", "..\benchmarks\csharp\csharp_benchmark.csproj", "{C62D5809-3059-4633-A267-31B402FCBFDA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {B5A73329-1C34-4D33-8013-D030983A59FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5A73329-1C34-4D33-8013-D030983A59FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5A73329-1C34-4D33-8013-D030983A59FF}.Release|Any CPU.Build.0 = Release|Any CPU + {C62D5809-3059-4633-A267-31B402FCBFDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C62D5809-3059-4633-A267-31B402FCBFDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C62D5809-3059-4633-A267-31B402FCBFDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C62D5809-3059-4633-A267-31B402FCBFDA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/csharp/lib/glide.csproj b/csharp/lib/glide.csproj index f8cc254da1..b890217632 100644 --- a/csharp/lib/glide.csproj +++ b/csharp/lib/glide.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 Glide enable enable @@ -11,10 +11,18 @@ - - - PreserveNewest - %(FileName)%(Extension) + + + PreserveNewest + %(FileName)%(Extension) + + + PreserveNewest + %(FileName)%(Extension) + + + PreserveNewest + %(FileName)%(Extension) diff --git a/csharp/tests/tests.csproj b/csharp/tests/tests.csproj index ce974b7cb9..c3b933a683 100644 --- a/csharp/tests/tests.csproj +++ b/csharp/tests/tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net8.0 enable enable From d6d836aacbf4dfd6ef463f01e2aabfbb8ab99a50 Mon Sep 17 00:00:00 2001 From: Guriy Samarin Date: Thu, 1 Feb 2024 21:34:02 +0000 Subject: [PATCH 2/7] Update csharp.yml --- .github/workflows/csharp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 2a015bae89..f12d8eda2c 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -54,7 +54,7 @@ jobs: - name: Test working-directory: ./csharp - run: dotnet test + run: dotnet test --framework net6.0 - uses: ./.github/workflows/test-benchmark with: From fd4d619182b02bb1708bc0765d19fd6e5ac6e9ae Mon Sep 17 00:00:00 2001 From: Guriy Samarin Date: Thu, 1 Feb 2024 21:42:04 +0000 Subject: [PATCH 3/7] Update install_and_test.sh --- benchmarks/install_and_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/install_and_test.sh b/benchmarks/install_and_test.sh index d193167d8d..7d3d8429bf 100755 --- a/benchmarks/install_and_test.sh +++ b/benchmarks/install_and_test.sh @@ -68,7 +68,7 @@ function runCSharpBenchmark(){ cd ${BENCH_FOLDER}/csharp dotnet clean dotnet build --configuration Release - dotnet run --configuration Release --resultsFile=../$1 --dataSize $2 --concurrentTasks $concurrentTasks --clients $chosenClients --host $host --clientCount $clientCount $tlsFlag $portFlag $minimalFlag + dotnet run --framework net6.0 --configuration Release --resultsFile=../$1 --dataSize $2 --concurrentTasks $concurrentTasks --clients $chosenClients --host $host --clientCount $clientCount $tlsFlag $portFlag $minimalFlag } function runJavaBenchmark(){ From e07e59f0262942ff43da60b7582a858d4606df20 Mon Sep 17 00:00:00 2001 From: "guriysamarin@outlook.com" Date: Sun, 4 Feb 2024 14:03:05 +0000 Subject: [PATCH 4/7] addressing comment --- csharp/DEVELOPER.md | 5 ++--- csharp/README.md | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/csharp/DEVELOPER.md b/csharp/DEVELOPER.md index cbc0d9b060..9532b8630a 100644 --- a/csharp/DEVELOPER.md +++ b/csharp/DEVELOPER.md @@ -12,7 +12,7 @@ The GLIDE C# wrapper consists of both C# and Rust code. Software Dependencies -- .net sdk 6 or 8 +- .net sdk 6 or later - git - GCC - pkg-config @@ -24,7 +24,7 @@ Software Dependencies **Dependencies installation for MacOS** visit https://dotnet.microsoft.com/en-us/download/dotnet -to download .net 6 and 8 installer +to download .net installer ```bash brew update brew install git gcc pkgconfig protobuf openssl @@ -67,7 +67,6 @@ git submodule update --init --recursive 2. Execute the following command from the root project folder: ```bash - docker run --name some-redis -d redis -p 6379:6379 cd benchmarks/csharp dotnet run --framework net8.0 --dataSize 1024 --resultsFile test.json --concurrentTasks 4 --clients all --host localhost --clientCount 4 ``` diff --git a/csharp/README.md b/csharp/README.md index 3f88f28591..325a98ef9e 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -26,8 +26,6 @@ The beta release of GLIDE for Redis was tested on Intel x86_64 using Ubuntu 22.0 .net 6.0 or higher. -## Installation and Setup - ## Basic Example ```csharp From 9e2001087637949efd32b27f4b02b82973ab3bd0 Mon Sep 17 00:00:00 2001 From: "guriysamarin@outlook.com" Date: Sun, 4 Feb 2024 14:08:09 +0000 Subject: [PATCH 5/7] returning launch.json sections --- .vscode/launch.json | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9962d2785a..7f53347c61 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,26 +1,29 @@ { "version": "0.2.0", "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/csharp/tests/bin/Debug/net6.0/tests.dll", + "args": [], + "cwd": "${workspaceFolder}/csharp/tests", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, { "name": "C# benchmark Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceFolder}/benchmarks/csharp/bin/Debug/net8.0/csharp_benchmark.dll", - "args": [ - "--dataSize", - "1024", - "--resultsFile", - "test.json", - "--concurrentTasks", - "4", - "--clients", - "all", - "--host", - "localhost", - "--clientCount", - "4" - ], + "args": [], "cwd": "${workspaceFolder}/benchmarks/csharp", "console": "internalConsole", "stopAtEntry": true From 9faab6039dc1f2c62b1642660dcffad4cdc337bb Mon Sep 17 00:00:00 2001 From: "guriysamarin@outlook.com" Date: Sun, 4 Feb 2024 14:43:40 +0000 Subject: [PATCH 6/7] address comments --- csharp/DEVELOPER.md | 16 +++++----------- csharp/README.md | 4 ---- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/csharp/DEVELOPER.md b/csharp/DEVELOPER.md index 9532b8630a..ca5c9369b4 100644 --- a/csharp/DEVELOPER.md +++ b/csharp/DEVELOPER.md @@ -49,19 +49,13 @@ git submodule update --init --recursive 3. Build the c# wrapper: Choose a build option from the following and run it from the `csharp` folder: - 1. Build in release mode, stripped from all debug symbols: + Build in release mode, stripped from all debug symbols: - ```bash - dotnet build - ``` - - 2. Build with debug symbols: - - ```bash - dotnet build --debug - ``` + ```bash + dotnet build + ``` -4. Run tests: +4. Run benchmark: 1. Ensure that you have installed redis-server and redis-cli on your host. You can find the Redis installation guide at the following link: [Redis Installation Guide](https://redis.io/docs/install/install-redis/install-redis-on-linux/). diff --git a/csharp/README.md b/csharp/README.md index 325a98ef9e..860f5e67c0 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -18,10 +18,6 @@ This preview release is recommended for testing purposes only. # Getting Started - C# Wrapper -## System Requirements - -The beta release of GLIDE for Redis was tested on Intel x86_64 using Ubuntu 22.04.1, Amazon Linux 2023 (AL2023), and macOS 12.7. - ## .net sdk supported version .net 6.0 or higher. From ee3be2ecab4702f21567c7f4d2d185ba7608b5c0 Mon Sep 17 00:00:00 2001 From: Guriy Samarin Date: Mon, 5 Feb 2024 17:52:57 +0000 Subject: [PATCH 7/7] up c# syntax, make cli defaults in one style --- benchmarks/csharp/Program.cs | 30 +++---- csharp/lib/AsyncClient.cs | 160 ++++++++++++++++----------------- csharp/lib/Logger.cs | 129 +++++++++++++------------- csharp/lib/Message.cs | 24 +---- csharp/lib/MessageContainer.cs | 82 ++++++++--------- 5 files changed, 196 insertions(+), 229 deletions(-) diff --git a/benchmarks/csharp/Program.cs b/benchmarks/csharp/Program.cs index 541e3100cd..f1aaabd34e 100644 --- a/benchmarks/csharp/Program.cs +++ b/benchmarks/csharp/Program.cs @@ -16,30 +16,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; } - [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 concurrentTasks { get; set; } - [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; } - [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; } [Option('C', "clientCount", Required = false, HelpText = "Number of clients to run concurrently", Default = new[] { 1 })] public IEnumerable clientCount { get; set; } - [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; @@ -118,10 +118,8 @@ private static double calculate_latency(IEnumerable 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( diff --git a/csharp/lib/AsyncClient.cs b/csharp/lib/AsyncClient.cs index b50fe77bf4..459407704a 100644 --- a/csharp/lib/AsyncClient.cs +++ b/csharp/lib/AsyncClient.cs @@ -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 GetAsync(string key) - { - var message = messageContainer.GetMessageForCall(key, null); - GetFfi(clientPointer, (ulong)message.Index, message.KeyPtr); - return await message; - } + public async Task 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 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 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 messageContainer = new(); + private readonly MessageContainer 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 } diff --git a/csharp/lib/Logger.cs b/csharp/lib/Logger.cs index da9e172090..7edc16f16c 100644 --- a/csharp/lib/Logger.cs +++ b/csharp/lib/Logger.cs @@ -6,83 +6,80 @@ using System.Text; -namespace Glide -{ - // TODO - use a bindings generator to create this enum. - public enum Level - { - Error = 0, - Warn = 1, - Info = 2, - Debug = 3, - Trace = 4 - } +namespace Glide; - /* - A class that allows logging which is consistent with logs from the internal rust core. - Only one instance of this class can exist at any given time. The logger can be set up in 2 ways - - 1. By calling init, which creates and modifies a new logger only if one doesn't exist. - 2. By calling setConfig, which replaces the existing logger, and means that new logs will not be saved with the logs that were sent before the call. - If no call to any of these function is received, the first log attempt will initialize a new logger with default level decided by rust core (normally - console, error). - */ - public class Logger - { - #region private fields +// TODO - use a bindings generator to create this enum. +public enum Level +{ + Error = 0, + Warn = 1, + Info = 2, + Debug = 3, + Trace = 4 +} - private static Level? loggerLevel = null; - #endregion private fields +/* +A class that allows logging which is consistent with logs from the internal rust core. +Only one instance of this class can exist at any given time. The logger can be set up in 2 ways - + 1. By calling init, which creates and modifies a new logger only if one doesn't exist. + 2. By calling setConfig, which replaces the existing logger, and means that new logs will not be saved with the logs that were sent before the call. +If no call to any of these function is received, the first log attempt will initialize a new logger with default level decided by rust core (normally - console, error). +*/ +public class Logger +{ + #region private fields - #region internal methods - // Initialize a logger instance if none were initialized before - this method is meant to be used when there is no intention to replace an existing logger. - // The logger will filter all logs with a level lower than the given level, - // If given a fileName argument, will write the logs to files postfixed with fileName. If fileName isn't provided, the logs will be written to the console. - internal static void Init(Level? level, string? filename = null) - { - if (Logger.loggerLevel is null) - { - SetLoggerConfig(level, filename); - } - } + private static Level? loggerLevel = null; + #endregion private fields - // take the arguments from the user and provide to the core-logger (see ../logger-core) - // if the level is higher then the logger level (error is 0, warn 1, etc.) simply return without operation - // if a logger instance doesn't exist, create new one with default mode (decided by rust core, normally - level: error, target: console) - // logIdentifier arg is a string contain data that suppose to give the log a context and make it easier to find certain type of logs. - // when the log is connect to certain task the identifier should be the task id, when the log is not part of specific task the identifier should give a context to the log - for example, "create client". - internal static void Log(Level logLevel, string logIdentifier, string message) + #region internal methods + // Initialize a logger instance if none were initialized before - this method is meant to be used when there is no intention to replace an existing logger. + // The logger will filter all logs with a level lower than the given level, + // If given a fileName argument, will write the logs to files postfixed with fileName. If fileName isn't provided, the logs will be written to the console. + internal static void Init(Level? level, string? filename = null) + { + if (Logger.loggerLevel is null) { - if (Logger.loggerLevel is null) - { - SetLoggerConfig(logLevel); - } - if (!(logLevel <= Logger.loggerLevel)) return; - log(Convert.ToInt32(logLevel), Encoding.UTF8.GetBytes(logIdentifier), Encoding.UTF8.GetBytes(message)); + SetLoggerConfig(level, filename); } - #endregion internal methods + } - #region public methods - // config the logger instance - in fact - create new logger instance with the new args - // exist in addition to init for two main reason's: - // 1. if GLIDE dev want intentionally to change the logger instance configuration - // 2. external user want to set the logger and we don't want to return to him the logger itself, just config it - // the level argument is the level of the logs you want the system to provide (error logs, warn logs, etc.) - // the filename argument is optional - if provided the target of the logs will be the file mentioned, else will be the console - public static void SetLoggerConfig(Level? level, string? filename = null) + // take the arguments from the user and provide to the core-logger (see ../logger-core) + // if the level is higher then the logger level (error is 0, warn 1, etc.) simply return without operation + // if a logger instance doesn't exist, create new one with default mode (decided by rust core, normally - level: error, target: console) + // logIdentifier arg is a string contain data that suppose to give the log a context and make it easier to find certain type of logs. + // when the log is connect to certain task the identifier should be the task id, when the log is not part of specific task the identifier should give a context to the log - for example, "create client". + internal static void Log(Level logLevel, string logIdentifier, string message) + { + if (Logger.loggerLevel is null) { - var buffer = filename is null ? null : Encoding.UTF8.GetBytes(filename); - Logger.loggerLevel = InitInternalLogger(Convert.ToInt32(level), buffer); + SetLoggerConfig(logLevel); } - #endregion public methods - - #region FFI function declaration - [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "log")] - private static extern void log(Int32 logLevel, byte[] logIdentifier, byte[] message); - - [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "init")] - private static extern Level InitInternalLogger(Int32 level, byte[]? filename); + if (!(logLevel <= Logger.loggerLevel)) return; + log(Convert.ToInt32(logLevel), Encoding.UTF8.GetBytes(logIdentifier), Encoding.UTF8.GetBytes(message)); + } + #endregion internal methods - #endregion + #region public methods + // config the logger instance - in fact - create new logger instance with the new args + // exist in addition to init for two main reason's: + // 1. if GLIDE dev want intentionally to change the logger instance configuration + // 2. external user want to set the logger and we don't want to return to him the logger itself, just config it + // the level argument is the level of the logs you want the system to provide (error logs, warn logs, etc.) + // the filename argument is optional - if provided the target of the logs will be the file mentioned, else will be the console + public static void SetLoggerConfig(Level? level, string? filename = null) + { + var buffer = filename is null ? null : Encoding.UTF8.GetBytes(filename); + Logger.loggerLevel = InitInternalLogger(Convert.ToInt32(level), buffer); } + #endregion public methods + + #region FFI function declaration + [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "log")] + private static extern void log(Int32 logLevel, byte[] logIdentifier, byte[] message); + [DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "init")] + private static extern Level InitInternalLogger(Int32 level, byte[]? filename); + #endregion } diff --git a/csharp/lib/Message.cs b/csharp/lib/Message.cs index 0263df7cd1..daa11602c6 100644 --- a/csharp/lib/Message.cs +++ b/csharp/lib/Message.cs @@ -38,7 +38,7 @@ public Message(int index, MessageContainer container) private T? result; private Exception? exception; - /// Triggers a succesful completion of the task returned from the latest call + /// Triggers a succesful completion of the task returned from the latest call /// to CreateTask. public void SetResult(T? result) { @@ -78,10 +78,7 @@ private void CheckRaceAndCallContinuation() } } - public Message GetAwaiter() - { - return this; - } + public Message GetAwaiter() => this; /// This returns a task that will complete once SetException / SetResult are called, /// and ensures that the internal state of the message is set-up before the task is created, @@ -124,20 +121,7 @@ public void OnCompleted(Action continuation) CheckRaceAndCallContinuation(); } - public bool IsCompleted - { - get - { - return completionState == COMPLETION_STAGE_CONTINUATION_EXECUTED; - } - } + public bool IsCompleted => completionState == COMPLETION_STAGE_CONTINUATION_EXECUTED; - public T? GetResult() - { - if (this.exception != null) - { - throw this.exception; - } - return this.result; - } + public T? GetResult() => this.exception is null ? this.result : throw this.exception; } diff --git a/csharp/lib/MessageContainer.cs b/csharp/lib/MessageContainer.cs index ddc19e3323..5999702363 100644 --- a/csharp/lib/MessageContainer.cs +++ b/csharp/lib/MessageContainer.cs @@ -4,67 +4,59 @@ using System.Collections.Concurrent; -namespace Glide +namespace Glide; + + +internal class MessageContainer { + internal Message GetMessage(int index) => messages[index]; - internal class MessageContainer + internal Message GetMessageForCall(string? key, string? value) { - internal Message GetMessage(int index) - { - return messages[index]; - } - - internal Message GetMessageForCall(string? key, string? value) - { - var message = GetFreeMessage(); - message.StartTask(key, value, this); - return message; - } + var message = GetFreeMessage(); + message.StartTask(key, value, this); + return message; + } - private Message GetFreeMessage() + private Message GetFreeMessage() + { + if (!availableMessages.TryDequeue(out var message)) { - if (!availableMessages.TryDequeue(out var message)) + lock (messages) { - lock (messages) - { - var index = messages.Count; - message = new Message(index, this); - messages.Add(message); - } + var index = messages.Count; + message = new Message(index, this); + messages.Add(message); } - return message; } + return message; + } - public void ReturnFreeMessage(Message message) - { - availableMessages.Enqueue(message); - } + public void ReturnFreeMessage(Message message) => availableMessages.Enqueue(message); - internal void DisposeWithError(Exception? error) + internal void DisposeWithError(Exception? error) + { + lock (messages) { - lock (messages) + foreach (var message in messages.Where(message => !message.IsCompleted)) { - foreach (var message in messages.Where(message => !message.IsCompleted)) + try { - try - { - message.SetException(new TaskCanceledException("Client closed", error)); - } - catch (Exception) { } + message.SetException(new TaskCanceledException("Client closed", error)); } - messages.Clear(); + catch (Exception) { } } - availableMessages.Clear(); + messages.Clear(); } - - /// This list allows us random-access to the message in each index, - /// which means that once we receive a callback with an index, we can - /// find the message to resolve in constant time. - private List> messages = new(); - - /// This queue contains the messages that were created and are currently unused by any task, - /// so they can be reused y new tasks instead of allocating new messages. - private ConcurrentQueue> availableMessages = new(); + availableMessages.Clear(); } + /// This list allows us random-access to the message in each index, + /// which means that once we receive a callback with an index, we can + /// find the message to resolve in constant time. + private List> messages = new(); + + /// This queue contains the messages that were created and are currently unused by any task, + /// so they can be reused y new tasks instead of allocating new messages. + private ConcurrentQueue> availableMessages = new(); }