Alec
Alec

Reputation: 32309

How to do a `getOrElseComplete` on `Promise`?

Does it make sense to have an operation like getOrElseComplete that tries to complete a Promise with a value but, if the Promise is already completed, returns the existing completed value instead. Here's a sample implementation:

implicit class PromiseOps[T](promise: Promise[T]) {
  def getOrElseComplete(value: Try[T]): Try[T] = {
    val didComplete = promise.tryComplete(value)
    if (didComplete) {
      value
    } else {
      // The `tryComplete` returned `false`, which means the promise
      // was already completed, so the following is safe (?)
      promise.future.value.get
    }
  }
}

Is this safe to do? If not, why not? If so, is there a way to do this directly (eg. without relying on questionable things like _.value.get) or why isn't there such a way in the standard library?

Upvotes: 2

Views: 109

Answers (2)

ghik
ghik

Reputation: 10764

From your comments it seems to me that this is a valid solution for your problem but I also feel that a method like this doesn't belong in Promise API because Promise is supposed to be only a settable "backend" of its Future.

I'd prefer to have a completely separate function which could look like this:

def getOrComplete[T](promise: Promise[T], value: Try[T]): Try[T] =
  promise.future.value.getOrElse {
    if (promise.tryComplete(value)) value
    else getOrComplete(promise, value)
  }

The recursive call may seem weird - it serves two purposes:

  • it protects against a race condition where some other thread completes the future just before we call tryComplete
  • it avoids usage of .value.get on the Future

You might also want to pass value as a by-name parameter to avoid evaluating it when the Promise is already completed.

Upvotes: 2

Martijn
Martijn

Reputation: 12102

This operation does what it promises. It may make more sense to take value by name, and don't try to complete if already completed, maybe something like

def getOrElseComplete(value: => Try[T]): Try[T] = {
  if (!promise.completed) {
    promise.tryComplete(value)
  }
  promise.future.value.get
}

It's kinda dodgy though. Sharing a promise and having multiple places where it might be completed sounds like a difficult to maintain design, and one has to ask what's happening with the other path that might still complete the Promise? Shouldn't something be cancelled there?

Upvotes: 1

Related Questions