cool breeze
cool breeze

Reputation: 4811

If you don't block on a future, can it cause a race like condition?

Say I have a controller that has a future call, and then I redirect to another page which expects that call to have computed. Is it possible the future doesn't return fast enough and when I redirect to the other page the data will not be "fresh"?

Example:

class HomeController  {

  def action1 = Action{

    val f1 = Future { ... }
    Redirect(routes.HomeController.action2)
   }

  def action2 = Action {

     // expects the call to f1:Future to have executed
   }
}

The reason I am asking is should my Service layer return Future's or block? Or should I pass the Future down to the calling code like in my controller.

UserService
  save
  delete
  update
  findById
  etc. etc..

Should these return Future?

If I expected the call to have computed, I have to block. So is there no hard and fast rule for this?

Upvotes: 0

Views: 63

Answers (2)

Arne Claassen
Arne Claassen

Reputation: 14414

There's a couple of things in your question that are somewhat tangential, so I'll address them in turn

1) Will the redirect response return before the future returns?

Impossible to tell and, further, it will not behave consistently. For the redirect to not return before the Future has completed its computation, you should use an async action instead, so that the call doesn't respond with the redirect until the Future completes:

def action1 = Action.async { request =>
  futureComputation().map { _ => Redirect(routes.HomeController.action2) }
}

2) Should my Service layer return a Future?

The simple answer is yes, if the underlying API is also non-blocking.

The more nuanced answer is yes, even if your service call is blocking, but every other call that is used in your workflow returns a future. This second qualification is about composition and readability.

Suppose the workflow is:

  • findById
  • fetchHttpResource (async get)
  • update (with data from fetch

Because there is one async component, the workflow should be async and you might write it like this:

def workflow(id:UUID):Future[Unit] = {
  val user = UserService.findById(id)
  fetchHttpResource(id).map { resource =>
    val updatedUser = user.copy(x = resource)
    UserService.update(updatedUser)
  }
}

If UserService returns Future's as well, you could use a for-comprehension instead:

def workflow(id: UUID): Future[Unit] = for {
  user <- UserService.findById(id)
  resource <- fetchHttpResource(id)
  _ <- UserService.update(user.copy(x = resource))
} yield {}

which brings me to

3) Is there a hard and fast rule for using Future's?

Async code is a turtles all the way down affair. Once you have one async call in your chain, your chain has to return a Future and once you have that requirement, composition via for, map, flatMap, etc. and error handling becomes a lot cleaner among Future signatures. The additional benefit is that if you have a blocking call now, maybe in the future you can find an async API to use inside that service, your signature doesn't have to change.

For returning a Future from blocking calls that may fail the best policy is to wrap them with future {}. If however, you have computed data that cannot fail, using Future.successful() is the better choice.

Upvotes: 3

corn_dog
corn_dog

Reputation: 181

As your code stands your action1 will kick off the code executed in the future then send the redirect to the browser. The is no guarantee that the code in the future will be done before action2 is called.

Howver, assuming this is play framework, you can wait for the code in the future to complete without blocking the current thread, by using the map or onComplete methods on Future to register callbacks, in which you'd complete the request by sending the redirect. You'll need to change your Action to Action.async.

Upvotes: 0

Related Questions