While composing futures with a for-yield structure, some with side effects, some without, I introduced a race condition because a future depending on a side effect did not take the result of that side effecting future as an argument.
In short:
future b reads a value that is changed by a side effect from future a, but future a does not explicitly depend on the result of future b and could therefore happen before b finishes reading.
To solve the problem, my colleague introduced a dummy function taking as an argument the result of b and simply throwing it away. This was done to make the dependency explicit.
The actual code is here:
val futureConnection:Future[(Either[String, (Connection)],Boolean)] =
for {
scannerUser <- scanner.orFail("Scanning user not found")
scannedUser <- futureScannedUser.orFail("Scanned user not found")
existsInAnyDirection <- connections.existsInAnyDirection(scannerUser, scannedUser)
connection <- connections.createConnection(scannerUser, scannedUser, form.magicWord, existsInAnyDirection)
} yield {
(connection, existsInAnyDirection)
}
In this case, future b is
connections.existsInAnyDirection(scannerUser, scannedUser)
and future a with the dummy parameter is
connections.createConnection(scannerUser, scannedUser, form.magicWord, existsInAnyDirection)
Notice that the parameter existsInAnyDirection
is never used inside createConnection
. This effectively creates the dependency graph that createConnection cannot be initiated before existsInAnyDirection is completed.
Now for the question:
Is there a more sane way to make the dependency explicit?
Bonus Info
My own digging tells me, that the Scala Futures simply don't handle side effects very well. The methods on the Future trait that deal with side effects return Unit, whereas there could very well be results to read from a side effecting operation, i.e. error codes, generated ID's, any other meta info, really.