ExistsAsync never returns

#1

I’ve started using the *Async API methods in the 2.1 SDK (thank you…thank you…thank you) and everything is going fine except ExistsAsync(). The following call never returns:

bool exists = await bucket.ExistsAsync(key).ConfigureAwait(false);

#2

@Scotch -

Interesting, do you have logs you can share? Also:

  • Bucket type
  • Server version
  • Cluster configuration

I used NuGet to install 2.1.0 and ran a simple console app that checks for the existence of a key in beer-sample bucket and worked as expected; I’d like to see why it’s failing for you. Note that with async a method that doesn’t return is usually caused by an uncaught exception; the Task never completes.

-Jeff

#3

I’ve been doing some troubleshooting on this, but I don’t have much more to go on.

// Works
bucket.Exists(snapshotId)

// Never returns
bucket.ExistsAsync(snapshotId).Result;

// Never returns (permissive synchronization)
bucket.ExistsAsync(snapshotId).ConfigureAwait(false).GetAwaiter().GetResult()

If I run from a unit test or console application, all return fine. If run inside IIS (ASP.NET MVC WebApi 2 application) the latter 2 just hang. There is no stack trace because there are no exceptions.

The bucket type is Couchbase and the server version is 3.0.3.1716. The cluster has four (4) nodes of identical VMs in one group.

#4

@Scotch -

This works in an MVC App using VS 2013 :

 public async Task<ActionResult> Index()
 {
    var bucket = ClusterHelper.GetBucket("default");
    var result = await bucket.ExistsAsync("1000");
    return View();
 } 

I don’t suggest this usage:

  bucket.ExistsAsync(snapshotId).Result;

Because your using it synchronously (your not awaiting the result, your blocking).

The same with this:

  bucket.ExistsAsync(snapshotId).ConfigureAwait(false).GetAwaiter().GetResult()

This is essentially the same as the previous example except your not depending on the SynchronizationContext for the restore point.

As long as you use await you should be fine; optionally add the ConfigureAwait(false) to be sure.

-Jeff

#5

This is a valid transition from an asynchronous to a synchronous context:

bucket.ExistsAsync(snapshotId).ConfigureAwait(false).GetAwaiter().GetResult()

The point of TAP (Task Asynchronous Pattern) is to allow blocked threads to do other work instead of idling. Since, primarily, threads block on IO (eg. network request/response) the optimal use of TAP is in operations that depend on resources external to the processor. There is little benefit, and actually some cost, to making asynchronous methods which don’t ever block (thus, I doubt you’ll ever see async math and string functions in .NET). The overhead of allocating the continuation on the stack outweighs the benefit of releasing the thread to do something else.

See: https://msdn.microsoft.com/en-us/magazine/hh456402.aspx

For this reason, I typically don’t write asynchronous business code. If I’m building a typical middle-tier service (validate a request, run business rules, call a repository, run some more business rules, assemble the response), the only part that is asynchronous is the repository; everything else executes at processor speed already. The other main feature of TAP, cancellations, can be achieved with reset events if I have something that might take a while; like a sort.

So, back to our problem above. While the thread is in ExistsAsync() it can be re-purposed to do other work. When it comes back to the calling method though, it runs my code synchronously and not as a continuation.

Can you please try your test in this manner and see how it works for you?

#6

@Scotch -

I see your point; I was expecting that users wanting synchronous behavior would use the synchronous API. As you stated, the method does not complete when called synchronously. Can you create a bug ticket for this? You can do so here: http://issues.couchbase.com/browse/NCBC

-Jeff