From 46980fd01365e336b879fcf65151f908df62efc3 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:18:07 +0300 Subject: [PATCH] Optimizations for request execution happy path (#3143) Optimize for request happy path --- .../netty/server/ServerInboundHandler.scala | 8 +++--- .../main/scala/zio/http/RoutePattern.scala | 15 ++++++----- .../src/main/scala/zio/http/Routes.scala | 25 +++++++++++-------- .../main/scala/zio/http/codec/PathCodec.scala | 14 +++++------ 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/zio-http/jvm/src/main/scala/zio/http/netty/server/ServerInboundHandler.scala b/zio-http/jvm/src/main/scala/zio/http/netty/server/ServerInboundHandler.scala index 5c11953cb..74340d825 100644 --- a/zio-http/jvm/src/main/scala/zio/http/netty/server/ServerInboundHandler.scala +++ b/zio-http/jvm/src/main/scala/zio/http/netty/server/ServerInboundHandler.scala @@ -48,8 +48,8 @@ private[zio] final case class ServerInboundHandler( implicit private val unsafe: Unsafe = Unsafe.unsafe - private var routes: Routes[Any, Response] = _ - private var runtime: NettyRuntime = _ + private var handler: Handler[Any, Nothing, Request, Response] = _ + private var runtime: NettyRuntime = _ val inFlightRequests: LongAdder = new LongAdder() private val readClientCert = config.sslConfig.exists(_.includeClientCert) @@ -58,7 +58,7 @@ private[zio] final case class ServerInboundHandler( def refreshApp(): Unit = { val pair = appRef.get() - this.routes = pair._1 + this.handler = pair._1.toHandler this.runtime = new NettyRuntime(pair._2) } @@ -88,7 +88,7 @@ private[zio] final case class ServerInboundHandler( releaseRequest() } else { val req = makeZioRequest(ctx, jReq) - val exit = routes(req) + val exit = handler(req) if (attemptImmediateWrite(ctx, req.method, exit)) { releaseRequest() } else { diff --git a/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala b/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala index f0adad773..b73f5b09b 100644 --- a/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala +++ b/zio-http/shared/src/main/scala/zio/http/RoutePattern.scala @@ -172,16 +172,15 @@ object RoutePattern { tree.add(p, v) } + private val wildcardsTree = roots.getOrElse(Method.ANY, null) + def get(method: Method, path: Path): Chunk[A] = { - val wildcards = roots.get(Method.ANY) match { - case None => Chunk.empty - case Some(value) => value.get(path) + val forMethod = roots.getOrElse(method, null) match { + case null => Chunk.empty + case value => value.get(path) } - - (roots.get(method) match { - case None => Chunk.empty - case Some(value) => value.get(path) - }) ++ wildcards + if (wildcardsTree eq null) forMethod + else forMethod ++ wildcardsTree.get(path) } def map[B](f: A => B): Tree[B] = diff --git a/zio-http/shared/src/main/scala/zio/http/Routes.scala b/zio-http/shared/src/main/scala/zio/http/Routes.scala index 720a95968..5847d9524 100644 --- a/zio-http/shared/src/main/scala/zio/http/Routes.scala +++ b/zio-http/shared/src/main/scala/zio/http/Routes.scala @@ -245,20 +245,25 @@ final case class Routes[-Env, +Err](routes: Chunk[zio.http.Route[Env, Err]]) { s */ def toHandler(implicit ev: Err <:< Response): Handler[Env, Nothing, Request, Response] = { implicit val trace: Trace = Trace.empty + val tree = self.tree Handler .fromFunctionHandler[Request] { req => val chunk = tree.get(req.method, req.path) - - if (chunk.length == 0) Handler.notFound - else if (chunk.length == 1) chunk(0) - else { - // TODO: Support precomputed fallback among all chunk elements: - chunk.tail.foldLeft(chunk.head) { (acc, h) => - acc.catchAll { response => - if (response.status == Status.NotFound) h - else Handler.fail(response) + chunk.length match { + case 0 => Handler.notFound + case 1 => chunk(0) + case n => // TODO: Support precomputed fallback among all chunk elements + var acc = chunk(0) + var i = 1 + while (i < n) { + val h = chunk(i) + acc = acc.catchAll { response => + if (response.status == Status.NotFound) h + else Handler.fail(response) + } + i += 1 } - } + acc } } .merge diff --git a/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala index d9b3a9384..ead97b5da 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/PathCodec.scala @@ -788,7 +788,7 @@ object PathCodec { def get(path: Path): Chunk[A] = get(path, 0) - private def get(path: Path, from: Int, skipLiteralsFor: Set[Int] = Set.empty): Chunk[A] = { + private def get(path: Path, from: Int, skipLiteralsFor: Set[Int] = null): Chunk[A] = { val segments = path.segments val nSegments = segments.length var subtree = self @@ -801,7 +801,7 @@ object PathCodec { val segment = segments(i) // Fast path, jump down the tree: - if (!skipLiteralsFor.contains(i) && subtree.literals.contains(segment)) { + if ((skipLiteralsFor.eq(null) || !skipLiteralsFor.contains(i)) && subtree.literals.contains(segment)) { // this subtree segment have conflict with others // will try others if result was empty @@ -875,19 +875,19 @@ object PathCodec { // Might be some other matches because trailing matches everything: if (subtree ne null) { - subtree.others.get(SegmentCodec.trailing) match { - case Some(subtree) => - result = result ++ subtree.value - case None => + subtree.others.getOrElse(SegmentCodec.Trailing, null) match { + case null => () + case subtree => result = result ++ subtree.value } } if (trySkipLiteralIdx.nonEmpty && result.isEmpty) { trySkipLiteralIdx = trySkipLiteralIdx.reverse + val skipLiteralsFor0 = if (skipLiteralsFor eq null) Set.empty[Int] else skipLiteralsFor while (trySkipLiteralIdx.nonEmpty && result.isEmpty) { val skipIdx = trySkipLiteralIdx.head trySkipLiteralIdx = trySkipLiteralIdx.tail - result = get(path, from, skipLiteralsFor + skipIdx) + result = get(path, from, skipLiteralsFor0 + skipIdx) } result } else result