scala - 缓存并重新加载未来将变得无效

scala - cache and reload a future which will become invalid later

  private class FutMemorizer[T](valid: T => Boolean)(f: () => Future[T]) {

    private val ref = new AtomicReference[Promise[T]]

    @scala.annotation.tailrec
    final def get(): Future[T] = {
      val nullableRef = ref.get()
      val valid = checkPromise(ref.get())
      if(valid) {
        nullableRef.future
      } else {
        val p = Promise[T]
        val success = ref.compareAndSet(nullableRef, p)
        if(success) {
          p.completeWith(f())
          p.future
        } else {
          get()
        }
      }
    }

    private def checkPromise(nullable: Promise[T]) = {
      nullable != null && {
        nullable.future.value match {
          case None => true // future is not complete all caller should wait
          case Some(Success(v)) => valid(v)
          case _ => false
        }
      }
    }
  }

我正在实现一个只缓存 valid 未来值的 Future 存储器。

It must meet following requirements

  1. Futures created by f never executed paralleled
  2. get never return a invalid value (once invalid recall f() to reload)

我的实现是否正确?

是否有更多functional或更简单的方法来做到这一点(因为我很难证明 mime 的正确性)?

据我了解这是错误的:

p.completeWith(f())

调用者得到一个未来,它的值是(或有时会是)f() 返回的未来的值,但它不会在任何地方检查这个值满足或将满足 valid(...) ;如果需要时间,则在 f() 返回的结果未来正在进行时,对于其他来电者来说也是如此。只有当 f() 的结果完成时,下一个调用者才可能开始 "fixing" 它。

我可能会通过以下方式解决此问题(请参阅 fixed 方法),并进行一些风格上的更改:

class FutMemorizer[T](valid: T => Boolean)(f: () => Future[T]) {

  private val ref = new AtomicReference[Future[T]]

  @tailrec
  final def get: Future[T] = {
    val current = ref.get
    if (current != null && isValid(current)) current
    else {
      val p = Promise[T]
      val pf = p.future
      if (ref.compareAndSet(current, pf)) {
        p.completeWith(fixed(f))
        pf
      } else get
    }
  }

  private def fixed(f: () => Future[T]): Future[T] =
    f() flatMap { t =>
      if (valid(t)) Future.successful(t) else fixed(f)
    }

  private def isValid(future: Future[T]) = 
    future.value match {
      case None => true // future is not complete all caller should wait
      case Some(Success(v)) => valid(v)
      case _ => false
    }
}

至于你关于更实用的方法的问题,我 猜测 fvalid 对外部状态有影响并基于它们的计算在它上面(我想这是让存储器失效的意义所在)会严重阻碍它。

刚发现spray-cache已经有这个功能