com.avsystem.commons.redis.RedisConnectionClient.scala Maven / Gradle / Ivy
package com.avsystem.commons
package redis
import akka.actor.{ActorSystem, Props}
import akka.pattern.ask
import com.avsystem.commons.concurrent.RetryStrategy
import com.avsystem.commons.redis.RawCommand.Level
import com.avsystem.commons.redis.actor.RedisConnectionActor.PacksResult
import com.avsystem.commons.redis.actor.RedisOperationActor.OpResult
import com.avsystem.commons.redis.actor.{RedisConnectionActor, RedisOperationActor}
import com.avsystem.commons.redis.config.{ConfigDefaults, ConnectionConfig, ExecutionConfig}
import com.avsystem.commons.redis.exception.ClientStoppedException
/**
* Redis client that uses a single, non-reconnectable connection.
* This is the most "raw" client implementation and the only one capable of directly executing connection state
* changing commands like `AUTH`, `CLIENT SETNAME`, `WATCH`, etc.
*
* However, note that connection-setup commands like `AUTH` may also be specified in
* [[config.ConnectionConfig ConnectionConfig]]
* (which may also be specified for connections used by [[RedisNodeClient]] and [[RedisClusterClient]]).
*
* This type of client should only be used when requiring capability of manual handling of connection state.
* If you simply need a single-connection, reconnectable client, use [[RedisNodeClient]] with connection pool size
* configured to 1.
*/
final class RedisConnectionClient(
val address: NodeAddress = NodeAddress.Default,
val config: ConnectionConfig = ConnectionConfig()
)
(implicit system: ActorSystem) extends RedisClient with RedisConnectionExecutor { self =>
private val initPromise = Promise[Unit]()
private val connectionActor = {
val props = Props(new RedisConnectionActor(address, config.copy(reconnectionStrategy = RetryStrategy.never)))
.withDispatcher(ConfigDefaults.Dispatcher)
config.actorName.fold(system.actorOf(props))(system.actorOf(props, _))
}
connectionActor ! RedisConnectionActor.Open(mustInitiallyConnect = true, initPromise)
@volatile private[this] var failure = Opt.empty[Throwable]
private def ifReady[T](code: => Future[T]): Future[T] =
failure.fold(code)(Future.failed)
/**
* Waits until Redis connection is initialized. Note that you can call [[executeBatch]] and [[executeOp]]
* even if the connection is not yet initialized - requests will be internally queued and executed after
* initialization is complete.
*/
def initialized: Future[this.type] =
initPromise.future.mapNow(_ => this)
def executionContext: ExecutionContext =
system.dispatcher
def executeBatch[A](batch: RedisBatch[A], config: ExecutionConfig): Future[A] =
ifReady(connectionActor.ask(batch.rawCommandPacks.requireLevel(Level.Connection, "ConnectionClient"))(config.responseTimeout)
.map({ case pr: PacksResult => batch.decodeReplies(pr) })(config.decodeOn))
//TODO: don't ignore executionConfig.decodeOn
def executeOp[A](op: RedisOp[A], executionConfig: ExecutionConfig): Future[A] =
ifReady(system.actorOf(Props(new RedisOperationActor(connectionActor))).ask(op)(executionConfig.responseTimeout)
.mapNow({ case or: OpResult[A@unchecked] => or.get }))
def close(): Unit = {
val cause = new ClientStoppedException(address.opt)
failure = cause.opt
connectionActor ! RedisConnectionActor.Close(cause, stop = true)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy