FDBDataBase Usage from Java API

(gaurav) #1


I ran into a few issues while working with Java API. Could someone please check what I could be doing wrong here:

  1. method accepts an executor and documents

    “e the {@link Executor} to use for asynchronous callbacks”.

    However, method seems to not use it:

public <T> T run(Function<? super Transaction, T> retryable, Executor e) {
     Transaction t = this.createTransaction();

Should the this.createTransaction() be taking in the passed executor in the constructor?

  1. Deadlock with FDBDatabased.readAsync()

I have this trivial code snippet to set a key in DB with a unique id - I am using VersionStamp to derive the unique id and also want to return it to the caller in same call:

CompletableFuture<byte[]> create(final String key) {
    final byte[] b = new Tuple().add(key).pack();
    return db.runAsync((Function<Transaction, CompletableFuture<byte[]>>) tx -> {
        tx.mutate(MutationType.SET_VERSIONSTAMPED_VALUE, b, Tuple.from(Versionstamp.incomplete()).packWithVersionstamp());
        return tx.getVersionstamp();

It appears that runAsync() method is composing the return Future from my lambda function and chaining this Future with commit() post the future completion. Since this future cannot complete till commit() is called (future is fetching VersionStamp value), this leads to a deadlock.

 return AsyncUtil.composeHandleAsync(process.thenComposeAsync(returnVal ->
		trRef.get().commit().thenApply(o -> {

Am I using runAsync() method incorrectly here?


(Alec Grieser) #2

With regard to question 1, yeah, that’s a bug. The createTransaction parameter should pass along the executor given. Instead, I believe that this will use the executor associated with the database. Oops. Feel free to file this as an issue on GitHub.

With regard to question 2, that is essentially expected behavior. The runAsync retry loop will only call commit after the returned future has completed (so you could do something like read a key from database, make some write based on your read, and then commit). Instead, you need to do something like:

byte[] versionstamp = -> {
    // some operation with your transaction
    return tx.getVersionstamp();

This will synchronously commit your transaction, though, so you could instead do:

CompletableFuture<byte[]> versionstampFuture = db.runAsync(tx -> {
   // some operation with your transaction
   return CompletableFuture.completedFuture(tx.getVersionstamp());

What this does is return a future of a future of the versionstamp within your loop. The retry loop will wait for that future to complete* and then call commit. Because the future is already done, it will call commit essentially instantaneously after returning. Once the commit is done, the final callback (the thenCompose there) will unwrap the versionstamp from the returned future. It’s a little bit awkward, but I think it would work.

*Or, rather, set a callback for that future’s completion

(gaurav) #3

Appreciate the response. I will file the bug/pr for (1).