我有一些异步(ZIO(代码,需要对其进行测试。如果我使用Thread.sleep()
创建一个测试部件,它运行良好,并且我总是得到响应:
for {
saved <- database.save(smth)
result <- eventually {
Thread.sleep(20000)
database.search(...)
}
} yield result
但如果我使用eventually
中的timeout
和interval
进行了相同的逻辑,那么它永远不会正常工作(我得到了超时(:
for {
saved <- database.save(smth)
result <- eventually(timeout(Span(20, Seconds)), interval(Span(20, Seconds))) {
database.search(...)
}
} yield result
我不明白为什么timeout
和interval
的工作原理与Thread.sleep
不同。它应该做完全相同的事情。有人能向我解释一下,并告诉我应该如何更改此代码以不需要使用Thread.sleep()
吗?
假设database.search(...)
返回ZIO[]
对象。
CCD_ 11很可能在第一次尝试后立即成功。
它成功地创建了一个查询数据库的任务。然后在没有任何重试逻辑的情况下查询数据库。
关于如何使其发挥作用:
val search: ZIO[Any, Throwable, String] = ???
val retried: ZIO[Any with Clock, Throwable, Option[String]] = search.retry(Schedule.spaced(Duration.fromMillis(1000))).timeout(Duration.fromMillis(20000))
这样的东西应该行得通。但我相信还有更优雅的解决方案。
@simpadjo的另一个答案解决了"什么";非常简洁。我将添加一些额外的上下文,说明您为什么会看到这种行为。
for {
saved <- database.save(smth)
result <- eventually {
Thread.sleep(20000)
database.search(...)
}
} yield result
这里混合了三种不同的技术,这引起了一些混乱。
首先是ZIO,它是一个异步编程库,使用自己的自定义运行时和执行模型来执行任务。第二个是eventually
,它来自ScalaTest,用于通过有效地轮询值的状态来检查异步计算。第三,还有Thread.sleep
,它是一个Java api,它实际上挂起当前线程并阻止任务进行,直到计时器到期。
eventually
使用了一种简单的重试机制,根据您使用的是scala标准库中的正常值还是Future
而有所不同。基本上,它在块中运行代码,如果抛出,则会休眠当前线程,然后根据一些间隔配置重试,最终超时。值得注意的是,在这种情况下,行为是完全同步的,这意味着只要{}
中的值没有抛出异常,它就不会继续重试。
CCD_ 17是一个重载操作,在这种情况下,它有效地阻止了传递给CCD_ 18的功能进行20秒。这意味着当调用database.search
时,操作可能已经完成。
第二种变体不同,它立即执行eventually
块中的代码,如果它抛出异常,它将根据您提供的间隔/超时逻辑再次尝试。在这种情况下,保存可能尚未完成(如果最终一致,则可能尚未传播(。因为您返回的是一个ZIO
,它被设计为而不是抛出,而eventually
不理解ZIO
,所以它只会返回search
尝试,而没有重试逻辑。
公认的答案:
val retried: ZIO[Any with Clock, Throwable, Option[String]] = search.retry(Schedule.spaced(Duration.fromMillis(1000))).timeout(Duration.fromMillis(20000))
工作是因为retry
和timeout
使用内置的ZIO
运算符,确实了解如何将retry
和timeout
实际设置为ZIO
。这意味着如果搜索失败,retry
将处理它,直到它成功。