zhttp.service.HttpRuntime.scala Maven / Gradle / Ivy
package zhttp.service
import io.netty.channel.{ChannelHandlerContext, EventLoopGroup => JEventLoopGroup}
import io.netty.util.concurrent.{EventExecutor, Future, GenericFutureListener}
import zio._
import scala.collection.mutable
import scala.concurrent.{ExecutionContext => JExecutionContext}
import scala.jdk.CollectionConverters._
/**
* Provides basic ZIO based utilities for any ZIO based program to execute in a
* channel's context. It will automatically cancel the execution when the
* channel closes.
*/
final class HttpRuntime[+R](strategy: HttpRuntime.Strategy[R]) {
def unsafeRun(ctx: ChannelHandlerContext)(program: ZIO[R, Throwable, Any]): Unit = {
val rtm = strategy.runtime(ctx)
// Close the connection if the program fails
// When connection closes, interrupt the program
rtm
.unsafeRunAsyncWith(for {
fiber <- program.fork
close <- ZIO.succeed {
val close = closeListener(rtm, fiber)
ctx.channel().closeFuture.addListener(close)
close
}
_ <- fiber.join
_ <- ZIO.succeed(ctx.channel().closeFuture().removeListener(close))
} yield ()) {
case Exit.Success(_) => ()
case Exit.Failure(cause) =>
cause.failureOption.orElse(cause.dieOption) match {
case None => ()
case Some(_) => java.lang.System.err.println(cause.prettyPrint)
}
if (ctx.channel().isOpen) ctx.close()
}
}
def unsafeRunUninterruptible(ctx: ChannelHandlerContext)(program: ZIO[R, Throwable, Any]): Unit = {
val rtm = strategy.runtime(ctx)
rtm
.unsafeRunAsyncWith(program) {
case Exit.Success(_) => ()
case Exit.Failure(cause) =>
cause.failureOption.orElse(cause.dieOption) match {
case None => ()
case Some(_) => java.lang.System.err.println(cause.prettyPrint)
}
if (ctx.channel().isOpen) ctx.close()
}
}
private def closeListener(rtm: Runtime[Any], fiber: Fiber.Runtime[_, _]): GenericFutureListener[Future[_ >: Void]] =
(_: Future[_ >: Void]) => rtm.unsafeRunAsync(fiber.interrupt): Unit
}
object HttpRuntime {
def dedicated[R](group: JEventLoopGroup): URIO[R, HttpRuntime[R]] =
Strategy.dedicated(group).map(runtime => new HttpRuntime[R](runtime))
def default[R]: URIO[R, HttpRuntime[R]] =
Strategy.default().map(runtime => new HttpRuntime[R](runtime))
def sticky[R](group: JEventLoopGroup): URIO[R, HttpRuntime[R]] =
Strategy.sticky(group).map(runtime => new HttpRuntime[R](runtime))
sealed trait Strategy[R] {
def runtime(ctx: ChannelHandlerContext): Runtime[R]
}
object Strategy {
def dedicated[R](group: JEventLoopGroup): ZIO[R, Nothing, Strategy[R]] =
ZIO.runtime[R].map(runtime => Dedicated(runtime, group))
def default[R](): ZIO[R, Nothing, Strategy[R]] =
ZIO.runtime[R].map(runtime => Default(runtime))
def sticky[R](group: JEventLoopGroup): ZIO[R, Nothing, Strategy[R]] =
ZIO.runtime[R].map(runtime => Group(runtime, group))
case class Default[R](runtime: Runtime[R]) extends Strategy[R] {
override def runtime(ctx: ChannelHandlerContext): Runtime[R] = runtime
}
case class Dedicated[R](runtime: Runtime[R], group: JEventLoopGroup) extends Strategy[R] {
private val localRuntime: Runtime[R] = runtime.withExecutor {
Executor.fromExecutionContext(runtime.runtimeConfig.executor.yieldOpCount) {
JExecutionContext.fromExecutor(group)
}
}
override def runtime(ctx: ChannelHandlerContext): Runtime[R] = localRuntime
}
case class Group[R](runtime: Runtime[R], group: JEventLoopGroup) extends Strategy[R] {
private val localRuntime: mutable.Map[EventExecutor, Runtime[R]] = {
val map = mutable.Map.empty[EventExecutor, Runtime[R]]
for (exe <- group.asScala)
map += exe -> runtime.withExecutor {
Executor.fromExecutionContext(runtime.runtimeConfig.executor.yieldOpCount) {
JExecutionContext.fromExecutor(exe)
}
}
map
}
override def runtime(ctx: ChannelHandlerContext): Runtime[R] =
localRuntime.getOrElse(ctx.executor(), runtime)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy