twitter/finagle

finagle-redis: QUEUED responses fail with ClassCastException

Open

#611 opened on Apr 26, 2017

View on GitHub
 (6 comments) (1 reaction) (0 assignees)Scala (8,864 stars) (1,435 forks)batch import
help wanted

Description

When Redis returns a QUEUED response, the returned value is opaquely cast which causes runtime errors.

Expected behavior

I'm not sure what the right call here is. On one hand, the call to Redis was successful, so failing the Future doesn't seem right. On the other, returning any instance of T also seems wrong and changing everything to Option[T] would be an even larger change. One option is to wrap all results in some "Response" class to provide additional information about the result.

Actual behavior

On QUEUED from Redis, the result is cast to a Future[Nothing] (presumably to get the types working) from Future.done, which ends up being a ConstFuture(Return(Unit)). Callers see the Future as successful, however do not know the underlying value is a BoxedUnit, which leads to ClassCastExceptions when the value is passed to functions.

Example error:

java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to scala.collection.Seq
        at com.curalate.redis.client.RedisClientBase$$anonfun$mGet$1.apply(RedisClientBase.scala:73)

Steps to reproduce the behavior

  1. Issue a Redis transaction but do not block on the resultant Future nor attach callbacks
  2. Immediately make another Redis call and attach a callback that operates on the value
  3. More often than not, a ClassCastException or NoSuchMethodError will be thrown. One may have to retry multiple times, as this is a race condition (i.e. one request will reach Redis before the other)

Pseudo-code

val keys = Seq(/* keys */)
val cmds = Seq(/* cmds */)
redisClient.transaction(cmds)
redisClient.mGet(keys).map(keys.zip) // .zip takes an Iterable, so a BoxedUnit will throw

Contributor guide