Solution to Issue 27 - #28
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request provides a solution to Issue 27 by refining the synchronous retrieval of Couchbase buckets within asynchronous contexts. The core change involves adding Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request updates the CouchbaseServiceCollectionExtensions.cs file and README.md examples to correctly handle ValueTask when synchronously waiting for asynchronous operations by adding .AsTask() before calling .GetAwaiter().GetResult(). However, the review comments point out that the underlying "sync over async" pattern, even with this fix, is generally problematic and can lead to deadlocks. The reviewer suggests providing fully asynchronous service registration methods or documenting the risks, and updating the README.md examples to demonstrate safer, fully asynchronous initialization patterns.
| { | ||
| var cluster = sp.GetRequiredService<ICluster>(); | ||
| var bucket = cluster.BucketAsync("bucket-name").GetAwaiter().GetResult(); | ||
| var bucket = cluster.BucketAsync("bucket-name").AsTask().GetAwaiter().GetResult(); |
There was a problem hiding this comment.
The examples in the README demonstrate a 'sync over async' pattern by using .GetAwaiter().GetResult(). This is a dangerous practice that can lead to deadlocks. This applies to the ICluster registration on line 157 as well.
It would be better to show an example of how to perform this initialization asynchronously. For ASP.NET Core applications, this can be done in Program.cs before building the host.
For example:
// In Program.cs (Top-level statements)
var cluster = await Cluster.ConnectAsync(...);
var bucket = await cluster.BucketAsync(...);
var scope = bucket.Scope(...);
builder.Services.AddSingleton<ICluster>(cluster);
builder.Services.AddSingleton<IScope>(scope);
builder.Services.AddCouchbaseVectorStore();This avoids blocking and is a much safer pattern for consumers of your library to follow.
| { | ||
| var cluster = sp.GetRequiredService<ICluster>(); | ||
| var bucket = cluster.BucketAsync("bucket-name").GetAwaiter().GetResult(); | ||
| var bucket = cluster.BucketAsync("bucket-name").AsTask().GetAwaiter().GetResult(); |
There was a problem hiding this comment.
Similar to the previous example, this code uses the 'sync over async' anti-pattern (.GetAwaiter().GetResult()), which can cause deadlocks. This also applies to the ICluster registration on line 188.
Please consider updating the documentation to show a fully asynchronous initialization pattern, as it's much safer for users. An example of an async pattern:
// In Program.cs (Top-level statements with ASP.NET Core)
var cluster = await Cluster.ConnectAsync(...);
var bucket = await cluster.BucketAsync(...);
var scope = bucket.Scope(...);
builder.Services.AddSingleton<ICluster>(cluster);
builder.Services.AddSingleton<IScope>(scope);
builder.Services.AddCouchbaseVectorStore();There was a problem hiding this comment.
My recommendation would be the same as well. We don't have to bother about the ValueTask or GetAwaiter().GetResult and making sync call within async method.
// Create Couchbase instances
var clusterOptions = new ClusterOptions
{
ConnectionString = configuration["Couchbase:ConnectionString"],
UserName = configuration["Couchbase:Username"],
Password = configuration["Couchbase:Password"]
};
var cluster = await Cluster.ConnectAsync(clusterOptions);
var bucket = await cluster.BucketAsync(configuration["Couchbase:BucketName"]!);
var scope = bucket.Scope(configuration["Couchbase:ScopeName"]!);
// Using Kernel Builder
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.Services.AddSingleton<ICluster>(cluster);
kernelBuilder.Services.AddSingleton<IScope>(scope);
kernelBuilder.Services.AddCouchbaseVectorStore();
var kernel = kernelBuilder.Build();
subasish-couchbase
left a comment
There was a problem hiding this comment.
Decided to create new tickets for the changes as discussed
| { | ||
| var cluster = sp.GetRequiredService<ICluster>(); | ||
| var bucket = cluster.BucketAsync("bucket-name").GetAwaiter().GetResult(); | ||
| var bucket = cluster.BucketAsync("bucket-name").AsTask().GetAwaiter().GetResult(); |
There was a problem hiding this comment.
My recommendation would be the same as well. We don't have to bother about the ValueTask or GetAwaiter().GetResult and making sync call within async method.
// Create Couchbase instances
var clusterOptions = new ClusterOptions
{
ConnectionString = configuration["Couchbase:ConnectionString"],
UserName = configuration["Couchbase:Username"],
Password = configuration["Couchbase:Password"]
};
var cluster = await Cluster.ConnectAsync(clusterOptions);
var bucket = await cluster.BucketAsync(configuration["Couchbase:BucketName"]!);
var scope = bucket.Scope(configuration["Couchbase:ScopeName"]!);
// Using Kernel Builder
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.Services.AddSingleton<ICluster>(cluster);
kernelBuilder.Services.AddSingleton<IScope>(scope);
kernelBuilder.Services.AddCouchbaseVectorStore();
var kernel = kernelBuilder.Build();
| @@ -191,7 +191,7 @@ builder.Services.AddSingleton<ICluster>(sp => | |||
| builder.Services.AddSingleton<IScope>(sp => | |||
There was a problem hiding this comment.
We can avoid this simply by creating an instance and registering it as singleton.
This way we don't have to bother about ValueTask or GetAwaiter().GetResult and making sync call within async method.
Another thing we need to think along is why GetBucketASync() has a return type of ValueTask...may be the .Net SDK team knows the answer
// Create Couchbase instances
var clusterOptions = new ClusterOptions
{
ConnectionString = configuration["Couchbase:ConnectionString"],
UserName = configuration["Couchbase:Username"],
Password = configuration["Couchbase:Password"]
};
var cluster = await Cluster.ConnectAsync(clusterOptions);
var bucket = await cluster.BucketAsync(configuration["Couchbase:BucketName"]!);
var scope = bucket.Scope(configuration["Couchbase:ScopeName"]!);
// Using Kernel Builder
var kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.Services.AddSingleton<ICluster>(cluster);
kernelBuilder.Services.AddSingleton<IScope>(scope);
kernelBuilder.Services.AddCouchbaseVectorStore();
var kernel = kernelBuilder.Build();
#27