【问题标题】:Short circuiting the chain of CompletionStage短路 CompletionStage 链
【发布时间】:2021-05-21 05:59:31
【问题描述】:

我正在使用 Java 8,并且我正在尝试运行一系列 CompletionStage

我不想使用join()get(),我想明确地完成CompletionStage

我正在尝试运行两个数据库查询,第二个依赖于第一个查询的结果。我正在使用会话启动数据库事务,运行 write query1,write query2 并且只有当两者都成功时,我才想提交事务或将其回滚。 事务和会话是 Neo4j java API https://neo4j.com/docs/api/java-driver/current/org/neo4j/driver/async/AsyncSession.html#writeTransactionAsync-org.neo4j.driver.async.AsyncTransactionWork-

的一部分

在运行两个查询成功/失败后,我想关闭会话(标准数据库实践)

这是伪代码-

DB Session starts transaction
    run Write Query1
    run Write Query2
    
    if both are successful
       commit transaction
    else
       rollback transaction
close session

我想要实现的是,如果 query1/query2 失败,那么它应该只是回滚事务并关闭会话。

如果 Query1 的结果不正确(小于某个阈值),查询 1 也可以抛出 CustomException。在这种情况下,它应该回滚事务。我正在为每个查询回滚 exceptionally 块中的事务。

快乐路径在下面的代码中运行良好,但是当我想抛出 CustomException 时,不会调用 Query2 块,甚至永远不会调用 Completable.allOf

CompletableFuture<String> firstFuture = new CompletableFuture();
CompletableFuture<String> secondFuture = new CompletableFuture();
CompletableFuture<String> lastFuture = new CompletableFuture();


//Lambda that executes transaction
TransactionWork<CompletionStage<String>> runTransactionWork = transaction -> {

     //Write Query1
       transaction.runAsync("DB WRITE QUERY1") //Running Write Query 1
              .thenCompose(someFunctionThatReturnsCompletionStage)
              .thenApply(val -> {
                     //throw CustomException if value less then threshold
                     if(val < threshold){
                         throw new CustomException("Incorrect value found");
                     }else{
                       //if value is correct then complete future
                       firstFuture.complete(val);
                     }
                  firstQuery.complete(val);
              }).exceptionally(error -> {
                        //Since failure occured in Query1 want to roll back
                        transaction.rollbackAsync();
                        firstFuture.completeExceptionally(error);
                        throw new RuntimeException("There has been an error in first query " + error.getMessage());
                  });

         //after the first write query is done then run the second write query
         firstFuture.thenCompose(val -> transaction.runAsync("DB Write QUERY 2"))
                   .thenCompose(someFunctionThatReturnsCompletionStage)
                   .thenApply(val -> {                      
                       //if value is correct then complete
                       secondFuture.complete(val);
                     }
                   }).exceptionally(error -> {
                        //Incase of failure in Query2 want to roll back
                        transaction.rollbackAsync();
                        secondFuture.completeExceptionally(error);
                        throw new RuntimeException("There has been an error in second query " + error.getMessage());
                  });


   //wait for both to complete and then complete the last future
   CompletableFuture.allOf(firstFuture, secondFuture)
                    .handle((empty, ex) -> {
                        if(ex != null){
                            lastFuture.completeExceptionally(ex);
                        }else{
                            //commit the transaction
                            transaction.commitAsync();
                            lastFuture.complete("OK");
                        }

                        return lastFuture;
                    });

            return lastFuture;
}

 //Create a database session
 Session session = driver.session();

 //runTransactionWork is lambda that has access to transaction
 session.writeTransactionAsync(runTransactionWork)
      .handle((val, err) -> {
         if(val != null){
            session.closeAsync();
            //send message to some broker about success
         }else{
            //fail logic 
         }
      });


如何实现短路异常以确保事务回滚并直接进入会话异常块。

这些是我对基于不同用例调用的代码块的观察,注意这些是基于我在代码中放置的调试点 -

  1. 快乐的路径 - firstFuture(success) -> secondFuture(success) -> LastFuture (success) -> 会话块成功调用(工作正常)
  2. 第一个 Future 失败 - firstFuture(由于异常而失败)-> secondFuture(从未调用)-> LastFuture(从未调用)-> 会话块失败(从未调用)
  3. 第二个未来失败 - firstFuture(成功)-> secondFuture(由于异常而失败)-> LastFuture(从未调用)-> 会话块失败(从未调用)

我希望 #2 和 #3 也能正常工作,并且应该回滚相应的事务并关闭会话。

我的问题是,为什么当未来 completesExceptionally 之一时,allOfhandle 的例外部分不会被调用?

【问题讨论】:

  • 你能在这里提供一个简化的例子吗?我已经读了几次这个问题(加上只对你有意义的代码),我不明白你的问题。
  • @Eugene 我做了一些编辑,还添加了一些伪代码。如果现在可以理解,请告诉我。感谢您的宝贵时间。
  • 这里有点矛盾。一方面你说你只想在两者都成功时提交,但另一方面,你的观点 (2) 说你仍然想要执行第二个事务。这是为什么?如果第一个失败了,为什么要执行第二个?你还是回滚。你能澄清一下吗?
  • @Eugene 是的,我仍然想回滚,我正在本地调试以检查它是否达到调试点。所以这就是我添加(从未调用)的原因。你是对的,我不想调用第二个块。它应该只是回滚。

标签: java asynchronous exception java-8 completable-future


【解决方案1】:

当你抛出 CustomException 时,firstFuture 没有完成。事实上,它什么也没有发生。因为它没有完成(成功),所以:

firstFuture.thenCompose...

不会被执行。 thenCompose 的文档说:

当此阶段正常完成时,将调用给定函数,并将此阶段的结果作为参数...

由于情况并非如此,该代码显然不会被触发。因此,secondFuture 也不会发生任何事情,因此CompletableFuture::allOf 必须完全为零。可能是一个简化的例子会有所帮助:

public class CF {

  public static void main(String[] args) {
    CompletableFuture<Void> one = CompletableFuture.runAsync(CF::db1);
    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
    System.out.println(one.isCompletedExceptionally());

    CompletableFuture<Void> two = one.thenRun(CF::db2);

    System.out.println("first is done : " + FIRST_FUTURE.isDone());
    System.out.println("second is done : " + SECOND_FUTURE.isDone());
    CompletableFuture.allOf(FIRST_FUTURE, SECOND_FUTURE).thenRun(() -> {
      System.out.println("allOf");
    });
    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
  }

  private static final boolean FAIL = true;
  private static final CompletableFuture<String> FIRST_FUTURE = new CompletableFuture<>();
  private static final CompletableFuture<String> SECOND_FUTURE = new CompletableFuture<>();

  private static void db1() {
    if(FAIL) {
      throw new RuntimeException("failed one");
    } else {
      FIRST_FUTURE.complete("42");
    }
  }

  private static void db2() {
    System.out.println("Running");
    SECOND_FUTURE.complete("42");
  }

}

如果你运行它,你会发现什么都没有打印出来……


很遗憾,我不熟悉 Neo4j,但您很可能可以根据自己的需要调整此示例:

public class CF {

  public static void main(String[] args) {
    CompletableFuture<Void> one = CompletableFuture.runAsync(CF::db1);

    CompletableFuture<Void> terminal =
    one.whenComplete((ok, th) -> {
      if(th != null || FIRST_FUTURE.isCompletedExceptionally()) {
        // no need to schedule the second one, need to rollback whatever the first one did
        // transaction.rollbackAsync();
        System.out.println("rollback because first one failed");
        LAST_FUTURE.completeExceptionally(new RuntimeException("because first one failed"));
      } else {
        CompletableFuture<Void> two = CompletableFuture.runAsync(CF::db2);
        two.whenComplete((ok2, th2) -> {
          if(th2 != null || SECOND_FUTURE.isCompletedExceptionally()) {
            System.out.println("rollback because second one failed");
            // transaction.rollbackAsync();
            LAST_FUTURE.completeExceptionally(new RuntimeException("because second one failed"));
          } else {
            LAST_FUTURE.complete("OK");
          }
        });
      }
    });

    // simulate that someone will call this
    terminal.join();
    System.out.println(LAST_FUTURE.join());

  }

  private static final boolean FAIL_ONE = false;
  private static final boolean FAIL_TWO = true;
  private static final CompletableFuture<String> FIRST_FUTURE = new CompletableFuture<>();
  private static final CompletableFuture<String> SECOND_FUTURE = new CompletableFuture<>();
  private static final CompletableFuture<String> LAST_FUTURE = new CompletableFuture<>();

  private static void db1() {
    if(FAIL_ONE) {
      LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
      RuntimeException ex = new RuntimeException("failed one");;
      FIRST_FUTURE.completeExceptionally(ex);
    } else {
      FIRST_FUTURE.complete("42");
    }
  }

  private static void db2() {
    if(FAIL_TWO) {
      LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
      RuntimeException ex = new RuntimeException("failed one");;
      SECOND_FUTURE.completeExceptionally(ex);
    } else {
      SECOND_FUTURE.complete("42");
    }
  }

}

【讨论】:

  • 太好了,我认为您的第一个示例表明未来需要成功/正常完成才能调用后续步骤。如果我有 7-8 个步骤,那不会导致一些回调地狱吗?我了解您的解决方案,非常感谢您抽出宝贵的时间。
  • @cheddarDev 我不确定我是否在回调地狱中关注你......你能解释一下吗?
  • 当然,我们最终得到了嵌套回调。在 cmets 中很难发布伪代码,让我分享链接 - freecodecamp.org/news/…
  • @cheddarDev 我不知道 javascript 与这段代码有什么关系(它可能确实如此,但我最近根本不写 javascript)。我建议你打开一个新问题,也许人们会提供更多帮助
  • 感谢所有帮助。我也可以使用该技术并避免回调地狱。一定要学习新东西,谢谢!
猜你喜欢
  • 2016-07-24
  • 1970-01-01
  • 1970-01-01
  • 2013-03-11
  • 2019-07-22
  • 2021-09-25
  • 2013-08-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多