All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zhttp.service.HttpRuntime.scala Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC11
Show newest version
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