-
Notifications
You must be signed in to change notification settings - Fork 76
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
C#: [WIP] CSharp Client #3354
base: main
Are you sure you want to change the base?
C#: [WIP] CSharp Client #3354
Conversation
Added the implementation of CSharp Native Client in conjunction with the Rust library. New implementation is, as of now, capable to handle client creation and command invocation. Also introduced logging at various levels to help with debugging.
As of now, a few still remain unimplemented. Those are, albeit required, not implemented because i do not know what theese actually express, making FFI implementation complicated. So i need to educate myself on theese first
Introduces a new blocking `SendCommand` method in both the C# and Rust layers, with associated tests and FFI wiring.
# Conflicts: # csharp/lib/BaseClient.cs # csharp/lib/Internals/Message.cs # csharp/lib/glide.csproj # csharp/rust/src/lib.rs # csharp/tests/Integration/TestConfiguration.cs # csharp/tests/tests.csproj
Moved all files of `Valkey.Glide.AppHost` from the root to the `sources` folder for better project organization. Updated references in the solution and unit test project to reflect the new path structure. No functional changes were made to the application.
Refactored connection interface to utilize the new `ConnectionRequest` model, introducing enhanced configurability for TLS, periodic checks, retries, and more. Added multiple structs and enums under `InterOp` namespace for detailed request handling.
Introduced `ConnectionConfigBuilder` and its associated extensions to simplify and enhance the process of configuring `ConnectionRequest` instances.
Introduced the `Valkey.Glide.Hosting` library for managing connections using a connection string. This includes parsing logic, service collection extensions for DI, and comprehensive unit tests to ensure implementation correctness. Updated documentation and project files to integrate the new library.
Implemented functionality for executing GET and SET commands with extension methods in the Glide client. Added unit tests to ensure correct behavior for both existing and non-existent keys, as well as simple set operations.
Introduced AspireSample.AppHost, AspireSample.Worker, and AspireSample.ServiceDefaults projects to demonstrate distributed application patterns. These include configurations for OpenTelemetry, resilience, health checks, and service discovery. Updated solution file to include the newly added projects.
This updates the Glide library with a comprehensive suite of Redis set commands, supporting various combinations like conditional and expiration-based operations. It also introduces specialized exceptions (e.g., `GlideSetCommandFailedException`) for improved error handling and debugging. Unit testing dependencies and helper methods are updated to reflect these enhancements. A todo was added to add tests for every call against a real valkey instance.
(I'm not a maintainer) |
csharp/.editorconfig
Outdated
|
||
# ReSharper properties | ||
resharper_align_multiline_binary_patterns = true | ||
resharper_align_multiline_calls_chain = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate what is the goal of this change? I mean moving to ReSharper properties. Also consider having separate PR for this, please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, this started as a small PoC, and these are essentially my configuration files. I haven’t changed them back yet. I can do so if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an Open Source project, we should not take dependencies on proprietary tools like resharper. We should assume that many of our developers will not have licenses for this tool. Additionally, there are many CSharp and DotNet settings which we should not be eliminating without a deeper discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rider is no longer paid but just as free as visual studio community (for FOSS, license differences still exist)
|
||
# MSTest test Results | ||
[Tt]est[Rr]esult*/ | ||
[Bb]uild[Ll]og.* | ||
|
||
# NUnit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on this changes also? Also consider having separate PR for this, please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, this started as a small PoC. I achieved this by completely deleting the old folder and replacing everything, as seen in fb1afe5.
The .gitignore
is a merge of these default ones:
Let me know if any adjustments are needed or if i should roll back :)
|
||
<ItemGroup> | ||
<PackageReference Include="Aspire.Hosting.Testing" Version="9.1.0" /> | ||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using BenchmarkDotNet is nice touch. Do you see how to make it in line with other benchmark testing for different packages? I would say it is better to keep them in line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s definitely a way to make it align. However, I haven’t been able to run the full repository benchmarks yet. That said, I also haven’t spent much time on that part so far.
|
||
public async Task InitializeAsync() | ||
{ | ||
var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.Valkey_Glide_AppHost>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If i remember correctly var
wasn't recommended by current project code style. Why do we think it is required code style change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, this started as a small PoC, and these are essentially my configuration files; I haven’t changed them back yet. I can do so if needed.
That said, I’d like to point out that this approach aligns better with Rust’s typical style, where variables are mostly declared as let variable = value
rather than let variable: Type = value
. This follows modern coding practices by leveraging automatic type inference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While using idiomatic coding patterns is desired, we are also trying to keep various language wrappers aligned to the degree possible. We have maintainers who have depth in one or more of the client languages and following different coding standards for each language makes the transition and understanding of each client more difficult. While type inference is good for experienced developers, it can complicate understanding the code for developers who might not be familiar with a specific language.
WaitBehavior.StopOnResourceUnavailable | ||
); | ||
if (resourceEvent.Snapshot.HealthStatus is not HealthStatus.Healthy) | ||
throw new Exception("Cache is not healthy, aspire initialization failed."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's throw something more concrete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm open to suggestions! ^^
I just realized that I mistakenly used the resource name instead of the container type + name. Rephrasing it to something like: "Aspire initialization failed for Valkey Container: 'cache' is not Healthy."
seems like a good improvement.
|
||
namespace Valkey.Glide.InterOp.Native; | ||
|
||
[SuppressMessage("ReSharper", "InconsistentNaming")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the benefit of having ReSharper specific suppressers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right; i missed the justification.
Rider (or ReSharper) flags this because the naming matches exactly and doesn’t follow the default project-style naming. However, this is intentional since these are FFI structures, which should be kept closely aligned for consistency.
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" /> | ||
<PackageReference Include="NSubstitute" Version="5.3.0" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the benefit of using NSubstitute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s a mocking framework, primarily used to easily mock IGlideClient
for now, making syntax validation more straightforward.
The key benefit is that it eliminates the need to manually create fully functional mocking classes.
I would say it would be easier to review if we would have different PR for different changes. Review 30% and will take a look on the rest later. |
I'm having trouble running the benchmark script due to my setup - I’m on Windows without Go, Java, or Python installed. It’s on my to-do list, but I first need a way to run the benchmark in a container or possibly a VM.
The reason everything is in one PR is that it originally started as a Proof of Concept. I can try to split it up, but I won’t be able to preserve the commit messages since the PR initially came from a quick "here's how I’d do it" Reddit comment blanking the folder first. |
The benchmark script has flags to determine which language to run, so you don't need go or Java. I think you'll only need javascript, to run some of the preparation scripts |
csharp/.editorconfig
Outdated
|
||
# ReSharper properties | ||
resharper_align_multiline_binary_patterns = true | ||
resharper_align_multiline_calls_chain = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an Open Source project, we should not take dependencies on proprietary tools like resharper. We should assume that many of our developers will not have licenses for this tool. Additionally, there are many CSharp and DotNet settings which we should not be eliminating without a deeper discussion.
@@ -88,10 +88,24 @@ jobs: | |||
steps: | |||
- uses: actions/checkout@v4 | |||
|
|||
- name: Set up dotnet ${{ matrix.dotnet }} | |||
- name: Install Node on self-hosted runner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand the need for Node in this pipeline. Can you elaborate on the need?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied this over from a different PR (#3323),
{ | ||
Marshal.WriteByte(ptr, 0); // Is allocated on heap | ||
for (var i = 0; i < utf8.Length; i++) | ||
Marshal.WriteByte(ptr + i + 1, utf8Ptr[i]); |
Check failure
Code scanning / CodeQL
Unvalidated local pointer arithmetic Critical
GetBytes
{ | ||
Marshal.WriteByte(ptr, 1); // Is allocated on buffer (potentially stack) | ||
for (var i = 0; i < utf8.Length; i++) | ||
Marshal.WriteByte(ptr + i + 1, utf8Ptr[i]); |
Check failure
Code scanning / CodeQL
Unvalidated local pointer arithmetic Critical
GetBytes
Hi @X39, thank you so much for your contribution.
I'll do my best to help you with your code. I'll start with requirements and project standards - unfortunately, not all of them are easily accessible for the public. You also need to downmerge in your branch some other fixes I'm working on once we complete them - routing (#3353) and handling binary strings (#3347). You can see the project plan for C# client in #216. |
Feel free to close this PR or keep reviewing it as-is so I can integrate the changes once a final decision is made. Over the weekend, I'll be splitting up the branch and creating sequential PRs that build on each other. I’ll aim to cover everything by then! :) |
Thanks a lot @X39 for this huge contribution we are very happy about it. |
Well, I'm impressed! |
About
This PR introduces a substantial refactoring of the C# client, reshaping its structure and approach to better align with long-term maintainability and usability. Key changes include:
rust
folder rather than mixing Rust and C# within a singlelib
directory.Context
I want to acknowledge that this PR is quite far-reaching and significantly alters the existing implementation. It initially started as a small proof of concept intended for a Reddit post but quickly evolved into a broader redesign. Given its scope, I’d like to open the discussion for feedback—on both technical direction and overall approach—to ensure alignment with the goals of the C# contributors.
Remaining Work
.csproj
functions correctly across all platforms..csproj
to use appropriate folder structures and determine whether Cargo should generate multiple binaries or GitHub Actions should manage multipleglide_rs
library targets.Value
:Attribute
mapping.BigNumber
mapping.Push
mapping.from_redis
to allocate a single memory block for efficient FFI data transfer.glide_rs
to .NET by integrating a logging callback, allowing .NET-side configuration.Parameter
structure.Key Considerations for Merge
csharp.yml
GitHub Action workflow to accommodate Aspire-based testing, which may require adjustments to the test fixture.