QueryAsync never finishes

Possible duplicate of QueryAsync is never finished

Running 2 queries (on v3.3.6 of the .NET driver) seems impossible. Any attempt after the first query results in QueryAsync hanging forever and never returning a result (or error or anything).

//first call - runs fine from anywhere
var result = await DB.Execute<User>("SELECT * FROM bucket WHERE type='user' LIMIT 1");
// second call - fails from anywhere
var result2 = await DB.Execute<Thing>("SELECT * FROM bucket WHERE type='thing' LIMIT 1")

public class DB {
    public static async Task<IQueryResult<T>?> Execute<T>(string sql, string? bucketName = null) {
		await Connect(bucketName); //sets _cluster
		var result = await _cluster.QueryAsync<T>(sql, options => options.ScanConsistency(QueryScanConsistency.RequestPlus));
		if (result.Errors.Count <= 0) {
			return result;
		}
		return null;
	}
}

If I reverse the 2 queries above, then result2 works fine and result1 fails always.

Seems like the first execution of Execute always leaves the driver in some unrecoverable state.

@Robert_Mirabelle I suspect you’re running into the nature of the queries and Couchbase infrastructure. The way you’re running the query doesn’t actually complete the query, the query is actually streaming asynchronously. If you don’t call ToListAsync(), enumerate in a foreach loop, or do some other form of enumeration the query will still be “running”. You may also Dispose the result to stop. Combine this with the fact that a single query node can only run one query at a time (by default) and you may encounter the symptoms you’re seeing.

2 Likes

@btburnett3 thanks for the input

I am already awaiting each result like so:

var rows = await result.Rows.ToListAsync();
var rows1 = await result2.Rows.ToListAsync()

I falsely assumed this was implied. All callers call Execute and “complete” the query by by calling ToListAsync() on the returned Task<IQueryResult<T>?>

The problem remains. Only a single call to Execute will work.

1 Like

@Robert_Mirabelle

I tried to replicate this and I could not:

var rows1 = await cluster.QueryAsync<dynamic>("SELECT d.* from default as d").ConfigureAwait(false);
var rows2 = await cluster.QueryAsync<dynamic>("SELECT d.* from default as d").ConfigureAwait(false);

foreach(var r in await rows1.Rows.ToListAsync())
{
      Console.WriteLine(r);
}

foreach (var r in await rows2.Rows.ToListAsync())
{
     Console.WriteLine(r);
}

I tried this as well which worked:

var rows1 = await cluster.QueryAsync<dynamic>("SELECT d.* from default as d").ConfigureAwait(false);
var rows2 = await cluster.QueryAsync<dynamic>("SELECT d.* from default as d").ConfigureAwait(false);

await foreach(var r in rows1.Rows)
{
     Console.WriteLine(r);
}

await foreach (var r in rows2.Rows)
{
     Console.WriteLine(r);
}

Is it possible the blocking is happening on the call to Connect(bucketName)?

@jmorris Thanks for the input. Both of your examples are different than mine, so naturally, I expect replicating to fail. But perhaps the difference reveals something I can try. I notice you use ConfigureAwait(false). Is this necessary? If so, is this documented somewhere? Thanks again.

Hi @Robert_Mirabelle

In certain situations not using ConfigureAwait(false) can cause deadlocks and performance issues. There is some documentation related to the SDK here and all over the Internet for more specifics as its a .NET method.

Jeff

I managed to get the queries to finish by appending ConfigureAwait(false) to method DB.Execute:

public class DB {
    public static async Task<IQueryResult<T>?> Execute<T>(string sql, string? bucketName = null) {
		await Connect(bucketName); //sets _cluster
		var result = await _cluster.QueryAsync<T>(sql).ConfigureAwait(false);
		if (result.Errors.Count <= 0) {
			return result;
		}
		return null;
	}
}

But now I find myself checking all over the place for ANY async Task-returning methods that utilize couchbase. For instance, I have another method that returns a Task<MyClient> that itself calls DB.Execute:

public static async Task<MyClient?> Resolve() {
		//now ConfigureAwait is also required here
		var result = await DB.Execute<MyClient>("SELECT * FROM x").ConfigureAwait(false);
		if (result == null) {
			return null;
		}
		var clients = await result.Rows.ToListAsync();
		if (clients.Count == 0) {
			return null;
		}
		var client = clients.First();
		return client;
	}

then another method that calls the method above:

private async Task ProcessCase(string path) {
	//now ConfigureAwait is also required here
	_client = await Resolve().ConfigureAwait(false);
	if (_client == null) {
		throw new Exception("not resolved");
	}
        //do work
}
		

It seems that ConfigureAwait(false) is mysteriously required both for any method that uses the couchbase .NET SDK OR any method that CALLS any method that uses the SDK, regardless of depth. Can this really be true?

1 Like

@Robert_Mirabelle

Good to hear!

It’s not actually requirement of the SDK, but a related to how .NET execute Tasks. There are a ton of resources online regarding it. IMO, it’s really a bug in the design of the Task framework. Note that I can guess your on an earlier ASP.NET version as in the very latest versions (.NET 5 and 6 and Core 3.1 I think), its no longer required.

My project uses .NET 4.8 because the 3rd-party SDK I’m using appears to require it. (attempting to set the project to .NET5 or higher results in tons of errors). So it seems I may be stuck doing ConfigureAwait(false) everywhere.

I did not know that the ConfigureAwait is no longer required for newer .NET. That’s useful. Thanks again for the info.

1 Like

@Robert_Mirabelle

Just so it doesn’t bite you in the future, it’s important to understand that the requirement (or lack thereof) for ConfigureAwait is a bit more complicated. As a general rule, it is not required in ASP.NET Core or ASP.NET 5/6 applications. This is because, unlike ASP.NET 4.x, they do not use a restricted SynchronizationContext for each incoming HTTP request. ConfigureAwait(false) simply says “ignore the SynchronizationContext, whatever it may be”, making a no-op in newer .NET but making it important in ASP.NET 4.x.

However, there are other cases where a restricted SynchronizationContext may be in place. For example, even in .NET 6 WinForms or WPF will run a SynchronizationContext that marshals continuations onto a single UI thread. These may still require ConfigureAwait(false). For this reason, if you are creating a reusable library of any kind it is recommended to use ConfigureAwait(false), as you don’t know the nature of the environment that a consumer may use.

1 Like

This is an excellent explanation of the real issue at hand. Thanks for the post!