val future: Future[String] = ???
val future: Future[String] = ???
future.withTimeout(500.millis).onComplete {
case Success(value) => println(s"Succeed with $value")
case Failure(e: TimeoutException) => println("Timed out")
case Failure(e) => println(s"Failed with other error: $e")
}
implicit class FutureTimeoutOps[T](
val self: Future[T]
) extends AnyVal {
def withTimeout(
duration: FiniteDuration
)(implicit ec: ExecutionContext): Future[T] = {
???
}
}
implicit class FutureTimeoutOps[T](
val self: Future[T]
) extends AnyVal {
def withTimeout(
duration: FiniteDuration
)(implicit ec: ExecutionContext): Future[T] = {
Future.firstCompletedOf(Seq(
self,
???
))
}
}
implicit class FutureTimeoutOps[T](
val self: Future[T]
) extends AnyVal {
def withTimeout(
duration: FiniteDuration
)(implicit ec: ExecutionContext): Future[T] = {
Future.firstCompletedOf(Seq(
self,
delay(duration).map(_ => throw new TimeoutException)
))
}
}
val future: Future[String] = ???
val fallback: String = ???
future.withTimeout(500.millis).transform {
case Success(value) => Success(value)
case Failure(e: TimeoutException) => Success(fallback)
case Failure(e) => Failure(e)
}.map { x =>
???
}
future.withTimeoutTo(500.millis, "fallback")
def withTimeoutTo(
duration: FiniteDuration,
fallback: => T
)(implicit ec: ExecutionContext): Future[T] = {
Future.firstCompletedOf(Seq(
self,
delay(duration).map(_ => fallback)
))
}
def withTimeoutTo(
duration: FiniteDuration,
fallback: => T
)(implicit ec: ExecutionContext): Future[T] = {
val promise = Promise[T]()
promise.completeWith(self)
delay(duration).onComplete { _ =>
if (!promise.isCompleted) {
promise.complete(Try(fallback))
}
}
promise.future
}
def withTimeoutTo(
duration: FiniteDuration,
fallback: => T
)(implicit ec: ExecutionContext): Future[T] = {
val promise = Promise[Option[T]]()
promise.completeWith(self.map(Some(_)))
promise.completeWith(delay(duration).map(_ => None))
promise.future.map {
case Some(result) => result
case None => fallback
}
}
def withTimeoutTo(
duration: FiniteDuration,
fallback: => T,
resourceClose: T => Future[Unit]
)(implicit ec: ExecutionContext): Future[T] = {
val promise = Promise[Option[T]]()
promise.completeWith(self.map(Some(_)))
promise.completeWith(delay(duration).map(_ => None))
promise.future.map {
case Some(result) => result
case None =>
self.flatMap { resource =>
resourceClose(resource)
}
fallback
}
}
1. Реализация с фолбэком и без
2. Фолбэк должен срабатывать только при возникновении таймаута
3. Фолбэк должен учитывать гонки
4. Ресурсы должны закрываться
val io: IO[String] = ???
val io: IO[String] = ???
io.timeout(500.millis)
val io: IO[String] = ???
io.timeout(500.millis)
io.timeoutTo(500.millis, IO.pure("fallback"))
1. IO — ленивый, а значит нет проблем с сайд эффектами (см. мой доклад)
3. IO можно отменить
4. IO безопасно работает с ресурсами
2. Нет гонок — это учтено в либе
def readFirstLine(file: File): IO[String] = {
IO(new BufferedReader(new FileReader(file)))
.bracket { in =>
IO(in.readLine())
} { in =>
IO(in.close())
}
}
IO(acquire1).bracket { r1 =>
IO(acquire2).bracket { r2 =>
IO(acquire3).bracket { r3 =>
r1 + r2 + r3
}(_.close())
}(_.close())
}(_.close())
abstract class Resource[F[_], A] {
def use[B](
f: A => F[B]
)(implicit F: Bracket[F, Throwable]): F[B]
}
def make[F[_], A](
acquire: F[A]
)(
release: A => F[Unit]
): Resource[F, A]
val file: File = ???
val file: File = ???
val resource: Resource[IO, BufferedReader] = {
Resource.make(
IO(new BufferedReader(new FileReader(file)))
)(r => IO(r.close()))
}
val file: File = ???
val resource: Resource[IO, BufferedReader] = {
Resource.make(
IO(new BufferedReader(new FileReader(file)))
)(r => IO(r.close()))
}
val res: IO[String] = {
resource.use { reader =>
IO(reader.readLine())
}
}
case class AppResources(
actorSystem: ActorSystem,
threadPool: ExecutionContext,
connectionPool: HikariPool,
)
val appResources: Resource[IO, AppResources] = for {
actorSystem <- actorSystemResource
threadPool <- threadPoolResource
connectionPool <- connectionPoolResource
} yield {
AppResources(actorSystem, threadPool, connectionPool)
}
appResources.use { appResources =>
// usage
}
val io: IO[String] = ???
val io: IO[String] = ???
for {
fiber <- io.start
_ <- fiber.cancel
} yield println("Started and cancelled")
def start(implicit cs: ContextShift[IO]): IO[Fiber[IO, A]]
def start(implicit cs: ContextShift[IO]): IO[Fiber[IO, A]]
trait Fiber[F[_], A] {
def join: F[A]
def cancel: CancelToken[F]
}
def start(implicit cs: ContextShift[IO]): IO[Fiber[IO, A]]
trait Fiber[F[_], A] {
def join: F[A]
def cancel: CancelToken[F]
}
type CancelToken[F[_]] = F[Unit]
def acquire: IO[String] = for {
_ <- IO(logger.info("Resource acquiring..."))
_ <- timer.sleep(5.seconds)
res <- IO {
logger.info("Resource acquired")
"some resource"
}
} yield res
def release(resource: String): IO[Unit] = {
IO(logger.info(s"Releasing $resource"))
}
val action: IO[Unit] = acquire.bracket { r =>
IO(logger.info(s"Using $r"))
}(release)
val result: IO[Unit] = action.timeoutTo(100.millis, IO {
logger.info("Resource acquiring timed out")
})
IO(logger.info("Started")) >> result.as(ExitCode.Success)
10:34:46.760 Started
10:34:46.804 Resource acquiring...
10:34:46.917 Resource acquiring timed out
10:34:51.806 Resource acquired
10:34:51.814 Releasing some resource
10:34:46.760 Started
10:34:46.804 Resource acquiring...
10:34:51.806 Resource acquired
10:34:51.812 Using some resource
10:34:51.814 Releasing some resource
10:34:51.817 Resource acquiring timed out
10:31:59.642 Started
10:31:59.715 Resource acquiring...
10:31:59.825 Resource acquiring timed out
10:32:04.736 Resource acquired
10:32:04.738 Releasing some resource
10:32:04.738 Using some resource
val action = acquire.bracket { r =>
Task(logger.info(s"Using $r"))
}(release)
val action = acquire.bracket { r =>
Task(logger.info(s"Using $r"))
}(release)
val action2 = acquire.bracket { r =>
Task.cancelBoundary >> Task(logger.info(s"Using $r"))
}(release)
10:34:24.758 Started
10:34:24.804 Resource acquiring...
10:34:24.910 Resource acquiring timed out
10:34:29.814 Resource acquired
10:34:29.816 Releasing some resource
12:38:45.952 Started
12:38:46.254 Resource acquiring...
12:38:51.331 Resource acquired
12:38:51.349 Releasing some resource
12:38:51.377 Resource acquiring timed out
def bracketInterruptAcquire[R <: Clock, E, A, B](
acquire: ZIO[R, E, A],
release: A => UIO[Any],
timeout: zio.duration.Duration,
)(
use: A => ZIO[R, E, B],
): ZIO[R, E, Option[B]] = {
ZIO.uninterruptibleMask { restore =>
for {
either <- acquire.uninterruptible.timeoutFork(timeout)
res <- either match {
case Left(fiber) =>
fiber.join.flatMap(release).fork *> ZIO.none
case Right(a) =>
restore(use(a).map(Some(_))).ensuring(release(a))
}
} yield res
}
}
13:19:40.973 Started
13:19:41.270 Resource acquiring...
13:19:41.724 Resource acquiring timed out
13:19:46.393 Resource acquired
13:19:46.396 Releasing some resource
1. Сделать правильно асинхронные таймауты ОЧЕНЬ сложно
3. Текущий cats-effect так не умеет by design
4. В Monix всё работает как нам надо, если не учитывать баг, который скоро доедет до мастера.
5. ZIO из коробки не содержит нужного комбинатора, но его можно сделать на существующих примитивах без проблем
6. В cats-effect 3, скорее всего, будет как в ZIO
2. C Future нужно пройти через кучу подводных камней