Response stream is not interrupted after mapping the effect type
See original GitHub issueI am having troubles interrupting a stream which is sent in a response using http4s. It happens only if I try to convert the effect type of the routes:
All imports are omitted to save the space. I am sorry for very fat code samples, but it’s the best way I could narrow it down.
Here is my routes definition:
class TestController[F[_] : Concurrent : Timer](deferred: Deferred[F, Unit]) extends Http4sDsl[F] {
private implicit val uuidEncoder: EntityEncoder[F, UUID] =
EntityEncoder
.stringEncoder[F]
.contramap(_.toString + "\n")
val routes: HttpRoutes[F] = HttpRoutes.of[F] {
case GET -> Root / "stream" =>
val stream = Stream
.repeatEval(Concurrent[F].delay(UUID.randomUUID()).flatTap(_ => Timer[F].sleep(2.seconds)))
.interruptWhen(deferred.get.attempt)
Ok(stream)
case POST -> Root / "complete" =>
deferred.complete(()).flatMap(_ => Ok("stream is cancelled"))
}
}
Here is how I instantiate those routes twice with multiple effects (IO[_]
and EitherT[IO, String, _]
) and then merge them into one route with final effect (IO[_]
) and two prefixes:
object Routes {
def createRoutes(
deferred1: Deferred[IO, Unit],
deferred2: Deferred[IO, Unit]
)(implicit c: Concurrent[IO], timer: Timer[IO]): HttpRoutes[IO] = {
type SpecialEff[T] = EitherT[IO, String, T]
val ioToSpecial: IO ~> SpecialEff = EitherT.liftK
val specialToIO: SpecialEff ~> IO =
new ~>[SpecialEff, IO] {
override def apply[A](fa: SpecialEff[A]): IO[A] = fa.value.flatMap {
case Left(_) => IO.raiseError(new RuntimeException("should never happen"))
case Right(res) => IO.pure(res)
}
}
val dsl = Http4sDsl[IO]
import dsl._
val ioTestRoutes: HttpRoutes[IO] = new TestController[IO](deferred1).routes
val specialTestRoutes: HttpRoutes[SpecialEff] =
new TestController[SpecialEff](deferred2.mapK(ioToSpecial)).routes
val convertedSpecialRoutes: HttpRoutes[IO] = Kleisli { requestIO =>
val requestSpecial =
Request[SpecialEff]( // no mapK cause it uses Stream#translate for the body (which makes stream uninterruptible)
method = requestIO.method,
uri = requestIO.uri,
httpVersion = requestIO.httpVersion,
headers = requestIO.headers,
body = requestIO.body.translateInterruptible(ioToSpecial),
attributes = requestIO.attributes
)
val specialResult: OptionT[SpecialEff, Response[SpecialEff]] =
specialTestRoutes(requestSpecial)
val ioResult: IO[Option[Response[IO]]] = specialResult.value
.map(_.map { specialResponse =>
Response[IO]( // no mapK cause it uses Stream#translate for the body (which makes stream uninterruptible)
status = specialResponse.status,
httpVersion = specialResponse.httpVersion,
headers = specialResponse.headers,
body = specialResponse.body.translateInterruptible(specialToIO)
)
})
.value
.flatMap {
case Right(maybeResp) =>
IO.pure(maybeResp)
case Left(error) =>
BadRequest(error).map(Some(_))
}
OptionT(ioResult)
}
Router[IO]("/io" -> ioTestRoutes, "/special" -> convertedSpecialRoutes)
}
}
I don’t know why, but /io/complete requests interrupt the stream opened with /io/stream just fine, but /special/complete ones do not interrupt the stream opened with /special/stream. Even though in both cases the corresponding Deferred instance is set successfully. I suspect that somehow I made the response body stream uninterruptible while mapping SpecialEff to IO or vice versa.
In case if that is an expected behaviour, could you please point me in the right direction to fix this? I am not expert in fs2 streams, but I don’t see any errors in the code.
Also, here is a sample project with this. It’s just three scala files, two of which are above: https://github.com/maximskripnik/stream-cancel-issue
Thanks in advance!
Issue Analytics
- State:
- Created 3 years ago
- Reactions:3
- Comments:9 (6 by maintainers)
@maximskripnik Cool, I released fs2 2.4.6 with that fix.
I asked our resident fs2 experts on Gitter to see if they can spot it, and once we find the answer, it might be an interesting problem to wind it back into http4s. This one has my interest, too. 😄