Detecting locked document with Java SDK 3

Using Java SDK v3, I can’t figure out a good way to determine if a document operations fails due to the document being pessimistically locked via getAndLock().

At first, after locking the document and attempting a write operation I would get a timeout error as the default BestEffortRetryStrategy automatically retries lock failures until we reach the KV timeout.

I worked around that by creating a custom retry strategy that does not retry on KV_LOCKED:

public class NoLockRetryStrategy extends BestEffortRetryStrategy {

    public static final NoLockRetryStrategy INSTANCE = new NoLockRetryStrategy();

    public NoLockRetryStrategy(){
        super();
    }

    @Override
    public CompletableFuture<RetryAction> shouldRetry(Request<? extends Response> request, RetryReason reason) {
        if (reason == RetryReason.KV_LOCKED) return CompletableFuture.completedFuture(RetryAction.noRetry());
        return super.shouldRetry(request, reason);
    }
}

However, even with this, I just end up getting a RequestCanceledException - although at least it happens immediately instead of waiting for the timeout. To work around this, I have to check the internals of that exception on each operation to see if the failure was due to the document being locked. Example:

        return getCollection().remove(docId, options)
                .onErrorMap(t -> {
                    if (t instanceof RequestCanceledException e) {
                        if (e.reason().innerReason() instanceof RetryReason reason 
                                && reason == RetryReason.KV_LOCKED) {
                            return new DocumentLockedException(null);
                        }
                    }
                    return t;
                });

That is a pretty ugly hack IMO just so that I can get locked document errors to throw something intelligent like a DocumentLockedException. Not to mention that I’m loosing all the error context that can be handy for debugging, etc.

Is there a better way?

Using Java SDK v3, I can’t figure out a good way to determine if a document operations fails due to the document being pessimistically locked via getAndLock().

At first, after locking the document and attempting a write operation I would get a timeout error as the default BestEffortRetryStrategy automatically retries lock failures until we reach the KV timeout.

Hi @tcoates. The raised TimeoutException will contain an error context with a set of retry reasons. You can inspect that to check if the set indicates the document was locked.

Yes, I understand that - it would be very similar to how I need to inspect the RequestCancelledException to determine if the document is locked.

The main challenge is that we’re migrating from using the v2 Java SDK where lock failures a) failed fast and b) threw a clear “document locked” exception that was easily handled/interpreted by the application.

Now using the v3 SDK, I understand the need for the timeout exception using the default retry strategy. Again, I’m able to override this behavior with my own retry strategy which solves problem “a”. Problem “b” is also solvable, albeit with some ugly code. My question remains - is there a better way? Could/should the SDK throw a DocumentLockedException in these cases?

1 Like

Hi Tom,

Thanks for letting us know about this pain point. We’re tracking it as feature request JVMCBC-1064 for inclusion in next month’s release of SDK 3.2.6.

If you’d like to experiment with the fix before then, please try the latest 3.2.6-SNAPSHOT and let us know how it works for you. (A guide for using a snapshot version is at the end of this post.)

With the new feature, can specify a custom exception by passing an exception translation function to RetryAction.noRetry, like this:

public class DocumentLockedException extends RuntimeException {
  public DocumentLockedException(Throwable cause) {
    super(cause);
  }
}

public class NoLockRetryStrategy extends BestEffortRetryStrategy {

  public static final NoLockRetryStrategy INSTANCE = new NoLockRetryStrategy();

  @Override
  public CompletableFuture<RetryAction> shouldRetry(
      Request<? extends Response> request,
      RetryReason reason
  ) {
    return reason == RetryReason.KV_LOCKED
        ? completedFuture(RetryAction.noRetry(DocumentLockedException::new))
        : super.shouldRetry(request, reason);
  }
}

This retry strategy can be applied globally by setting it on the ClusterEnvironment when connecting to the cluster:

Cluster cluster = Cluster.connect(
    "localhost",
    ClusterOptions.clusterOptions("Administrator", "password")
        .environment(env -> env.retryStrategy(NoLockRetryStrategy.INSTANCE)));

Using a snapshot version

Couchbase SDK snapshots are available in the Sonatype snapshot repository.

Maven

Add this section to your pom.xml:

<repositories>
    <repository>
        <id>sonatype-snapshots</id>
        <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        <releases><enabled>false</enabled></releases>
        <snapshots><enabled>true</enabled></snapshots>
    </repository>
</repositories>

Gradle (groovy)

Edit the “repositories” section to look like this:

repositories {
  mavenCentral() // or whatever your primary repo is

  maven {
    url "https://oss.sonatype.org/content/repositories/snapshots"
    mavenContent { snapshotsOnly() }
  }
}
1 Like

That looks like a great solution. I’ll look forward to that release when it’s ready. Thanks for your help!

-Tom

1 Like