@@ -26,6 +26,7 @@ import akka.util.duration._
2626
2727import scredis .commands .async ._
2828import scredis .exceptions ._
29+ import scredis .util .BlockingExecutor
2930
3031import scala .collection .mutable .ListBuffer
3132
@@ -139,19 +140,36 @@ final class Redis private[scredis] (
139140 private val timerTask = if (IsAutomaticPipeliningEnabled ) Some ((
140141 new Timer (
141142 config.Async .TimerThreadNamingPattern .replace(" $p" , PoolNumber ),
142- config. Async . Executors . DaemonThreads
143+ true
143144 ),
144145 new TimerTask () {
145146 def run (): Unit = executePipelineIfNeeded()
146147 }
147148 )) else None
148149
149- private val executorOpt = ecOpt match {
150+ private val internalExecutorOpt = ecOpt match {
150151 case Some (_) => None
151- case None => Some (newThreadPool())
152+ case None => Some (
153+ newThreadPool(
154+ config.Async .Executors .Internal .ThreadsNamingPattern ,
155+ config.Async .Executors .Internal .DaemonThreads ,
156+ config.Async .Executors .Internal .ThreadsPriority
157+ )
158+ )
152159 }
160+ private val interalExecutionContext = ecOpt.getOrElse(
161+ BlockingExecutor (internalExecutorOpt.get, config.Async .Executors .Internal .MaxConcurrent )
162+ )
153163
154- implicit val ec = ecOpt.getOrElse(ExecutionContext .fromExecutorService(executorOpt.get))
164+ private var shouldShutdownCallbackExecutor = false
165+ private lazy val callbackExecutor : ExecutorService = {
166+ shouldShutdownCallbackExecutor = true
167+ newThreadPool(
168+ config.Async .Executors .Callback .ThreadsNamingPattern ,
169+ config.Async .Executors .Callback .DaemonThreads ,
170+ config.Async .Executors .Callback .ThreadsPriority
171+ )
172+ }
155173
156174 private val pool = ClientPool (config)
157175
@@ -170,23 +188,26 @@ final class Redis private[scredis] (
170188 config.Client .Sleep
171189 )
172190
173- private def newThreadPool (): ExecutorService = {
191+ lazy implicit val ec = ecOpt.getOrElse(
192+ BlockingExecutor (callbackExecutor, config.Async .Executors .Callback .MaxConcurrent )
193+ )
194+
195+ private def execute [A ](f : => A ): Future [A ] = Future {
196+ f
197+ }(interalExecutionContext)
198+
199+ private def newThreadPool (
200+ namingPattern : String , daemon : Boolean , priority : Int
201+ ): ExecutorService = {
174202 val threadFactory = new BasicThreadFactory .Builder ()
175203 .namingPattern(
176- config. Async . Executors . ThreadsNamingPattern .replace(" $p" , PoolNumber ).replace(" $t" , " %d" )
204+ namingPattern .replace(" $p" , PoolNumber ).replace(" $t" , " %d" )
177205 )
178- .daemon(config. Async . Executors . DaemonThreads )
179- .priority(config. Async . Executors . ThreadsPriority )
206+ .daemon(daemon )
207+ .priority(priority )
180208 .build()
181209
182- new ThreadPoolExecutor (
183- config.Async .Executors .Threads ,
184- config.Async .Executors .Threads ,
185- 0L ,
186- TimeUnit .MILLISECONDS ,
187- new LinkedBlockingQueue [Runnable ](config.Async .Executors .QueueCapacity ),
188- threadFactory
189- )
210+ Executors .newCachedThreadPool(threadFactory)
190211 }
191212
192213 private def nextIndex (): Int = synchronized {
@@ -273,7 +294,7 @@ final class Redis private[scredis] (
273294 - 1
274295 }
275296 }
276- if (executeIndex >= 0 ) Future { executePipeline(executeIndex) }
297+ if (executeIndex >= 0 ) execute { executePipeline(executeIndex) }
277298 } catch {
278299 case e : Throwable => logger.error(" An unexpected error occurred" , e)
279300 } finally {
@@ -293,7 +314,11 @@ final class Redis private[scredis] (
293314 val (future, executeIndex) = synchronized {
294315 val commands = this .commands.get(index)
295316 val future = try {
296- val promise = Promise [Any ]()
317+ /*
318+ * Akka promises need explicit execution context.
319+ * Cannot re-use internal execution context otherwise it can create deadlocks.
320+ */
321+ val promise = Promise [Any ]()(ec)
297322 commands += ((body, opts, promise))
298323 promise.future.asInstanceOf [Future [A ]]
299324 } catch {
@@ -311,7 +336,7 @@ final class Redis private[scredis] (
311336 }
312337 (future, executeIndex)
313338 }
314- if (executeIndex >= 0 ) Future { executePipeline(executeIndex) }
339+ if (executeIndex >= 0 ) execute { executePipeline(executeIndex) }
315340 future
316341 } else {
317342 withClientAsync(body)
@@ -335,7 +360,7 @@ final class Redis private[scredis] (
335360 def borrowClient (): Client = pool.borrowClient()
336361 def returnClient (client : Client ): Unit = pool.returnClient(client)
337362 def withClient [A ](body : Client => A ): A = pool.withClient(body)
338- def withClientAsync [A ](body : Client => A ): Future [A ] = Future {
363+ def withClientAsync [A ](body : Client => A ): Future [A ] = execute {
339364 withClient(body)
340365 }
341366
@@ -357,7 +382,7 @@ final class Redis private[scredis] (
357382 */
358383 override def select (
359384 db : Int
360- )(implicit opts : CommandOptions = DefaultCommandOptions ): Future [Unit ] = Future {
385+ )(implicit opts : CommandOptions = DefaultCommandOptions ): Future [Unit ] = execute {
361386 selectInternal(db)
362387 }
363388
@@ -375,7 +400,7 @@ final class Redis private[scredis] (
375400 */
376401 override def auth (password : String )(
377402 implicit opts : CommandOptions = DefaultCommandOptions
378- ): Future [Unit ] = Future {
403+ ): Future [Unit ] = execute {
379404 authInternal(if (password.isEmpty) None else Some (password))
380405 }
381406
@@ -420,7 +445,7 @@ final class Redis private[scredis] (
420445 }
421446
422447 try {
423- executorOpt .foreach { executor =>
448+ internalExecutorOpt .foreach { executor =>
424449 executor.shutdown()
425450 awaitTerminationOpt.foreach { timeout =>
426451 if (timeout.isFinite) {
@@ -432,9 +457,18 @@ final class Redis private[scredis] (
432457 }
433458 } catch {
434459 case e : Throwable => logger.error(
435- " An error occurred while shutting down default executor" , e
460+ " An error occurred while shutting down internal executor" , e
436461 )
437462 }
463+ if (shouldShutdownCallbackExecutor) {
464+ try {
465+ callbackExecutor.shutdown()
466+ } catch {
467+ case e : Throwable => logger.error(
468+ " An error occurred while shutting down callback executor" , e
469+ )
470+ }
471+ }
438472 try {
439473 pool.close()
440474 } catch {
@@ -444,7 +478,9 @@ final class Redis private[scredis] (
444478 }
445479 }
446480
447- if (IsAutomaticPipeliningEnabled ) nextIndex()
481+ if (IsAutomaticPipeliningEnabled ) {
482+ nextIndex()
483+ }
448484
449485 timerTask.foreach {
450486 case (timer, task) => timer.scheduleAtFixedRate(task, Interval .toMillis, Interval .toMillis)
0 commit comments