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

zio.redis.internal.SingleNodeExecutor.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 John A. De Goes and the ZIO contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.redis.internal

import zio._
import zio.redis.internal.SingleNodeExecutor._
import zio.redis.{RedisConfig, RedisError}

private[redis] final class SingleNodeExecutor private (
  connection: RedisConnection,
  requests: Queue[Request],
  responses: Queue[Promise[RedisError, RespValue]],
  requestQueueSize: Int
) extends SingleNodeRunner
    with RedisExecutor {

  // TODO NodeExecutor doesn't throw connection errors, timeout errors, it is hanging forever
  def execute(command: RespCommand): UIO[IO[RedisError, RespValue]] =
    Promise
      .make[RedisError, RespValue]
      .flatMap(promise => requests.offer(Request(command.args.map(_.value), promise)).as(promise.await))

  def onError(e: RedisError): UIO[Unit] = responses.takeAll.flatMap(ZIO.foreachDiscard(_)(_.fail(e)))

  def send: IO[RedisError.IOError, Unit] =
    requests.takeBetween(1, requestQueueSize).flatMap { requests =>
      val bytes =
        requests
          .foldLeft(new ChunkBuilder.Byte())((builder, req) => builder ++= RespValue.Array(req.command).asBytes)
          .result()

      connection
        .write(bytes)
        .mapError(RedisError.IOError(_))
        .tapBoth(
          e => ZIO.foreachDiscard(requests)(_.promise.fail(e)),
          _ => ZIO.foreachDiscard(requests)(req => responses.offer(req.promise))
        )
        .unit
    }

  def receive: IO[RedisError, Unit] =
    connection.read
      .mapError(RedisError.IOError(_))
      .via(RespValue.Decoder)
      .collectSome
      .foreach(response => responses.take.flatMap(_.succeed(response)))

}

private[redis] object SingleNodeExecutor {
  lazy val layer: ZLayer[RedisConfig, RedisError.IOError, RedisExecutor] =
    RedisConnection.layer >>> makeLayer

  def local: ZLayer[Any, RedisError.IOError, RedisExecutor] =
    ZLayer.make[RedisExecutor](ZLayer.succeed(RedisConfig.Local), layer)

  def create(connection: RedisConnection): URIO[Scope & RedisConfig, SingleNodeExecutor] =
    for {
      requestQueueSize <- ZIO.serviceWith[RedisConfig](_.requestQueueSize)
      requests         <- Queue.bounded[Request](requestQueueSize)
      responses        <- Queue.unbounded[Promise[RedisError, RespValue]]
      executor          = new SingleNodeExecutor(connection, requests, responses, requestQueueSize)
      _                <- executor.run.forkScoped
      _                <- logScopeFinalizer(s"$executor Node Executor is closed")
    } yield executor

  private final case class Request(command: Chunk[RespValue.BulkString], promise: Promise[RedisError, RespValue])

  private def makeLayer: ZLayer[RedisConnection & RedisConfig, RedisError.IOError, RedisExecutor] =
    ZLayer.scoped(ZIO.serviceWithZIO[RedisConnection](create))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy