Monix: Version 2.2.2 Released

Version 2.2.2 is out now.

It is a minor release that’s binary and source compatible with 2.2.x.

In summary:

  • Implemented a circuit breaker for Task, for providing stability and preventing cascading failures in distributed systems
  • Added Task!.memoizeOnSuccess and Coeval!.memoizeOnSuccess operators that only caches the result (with idempotency guarantees) for successful results, while re-evaluating the source on failures
  • Added Task.deferFutureAction that helps with wrapping Future-enabled APIs without requiring ExecutionContext in the function signature, since Task can inject it for you
  • Added SingleAssignmentCancelable.plusOne for helping with building Consumer objects
  • Moved and redesigned exceptions in monix.execution
  • Task.sequence now has lazy behavior in evaluating the given sequence (previously we were doing a foldLeft on that sequence, which could have strict behavior, depending on the collection type)
  • Critical bugs fixed!

Details below!

New Features #

Issue #306 - Introduced a circuit breaker in monix.eval.TaskCircuitBreaker, see documentation:

A circuit breaker is used to provide stability and prevent cascading failures in distributed systems:

import monix.eval._
import scala.concurrent.duration._

val circuitBreaker = TaskCircuitBreaker(
  maxFailures = 5,
  resetTimeout = 10.seconds
)

circuitBreaker.protect(problematicTask.timeout(1.second))

Issue #312 - Introduced the Task!.memoizeOnSuccess operator, see documentation:

This Task!.memoizeOnSuccess operator, caches the result (with idempotency guarantees), just like the normal memoize operator, only it does so only on a successful result, whereas if failures happens, subsequent calls will retry evaluating the source until it succeeds:

var effect = 0

val source = Task.eval { 
  effect += 1
  if (effect < 3) throw new RuntimeException("dummy") 
  else effect
}

val cached = source.memoizeOnSuccess

val f1 = cached.runAsync // yields RuntimeException
val f2 = cached.runAsync // yields RuntimeException
val f3 = cached.runAsync // yields 3
val f4 = cached.runAsync // yields 3

Issue #313 - On Task we added the deferFutureAction builder, see documentation:

This helps with wrapping Future-enabled APIs, by being able to specify a callback with an injected Scheduler:

def sumFuture(list: Seq[Int])(implicit ec: ExecutionContext): Future[Int] =
  Future(list.sum)

// Look Ma, no implicit ExecutionContext!
def sumTask(list: Seq[Int]): Task[Int] =
  Task.deferFutureAction { implicit scheduler =>
    sumFuture(list)
  }

Issue #325: Added SingleAssignmentCancelable.plusOne builder, see documentation:

This plusOne function builds a SingleAssignmentCancelable reference which also cancels an extra cancelable specified at construction time, in addition to the underlying cancelable that gets assigned later:

val c = {
  val guest = Cancelable(() => println("extra canceled"))
  SingleAssignmentCancelable.plusOne(guest)
}

c := Cancelable(() => println("primary canceled"))

c.cancel()
//=> extra canceled
//=> primary canceled

This helps with building Consumer objects that need to specify a cancelable that cancels the data source, plus whatever resources the consumer itself has opened.

List of Changes #

New Features and Enhancements:

  • Issue #306: Circuit Breaker for Task
  • Issue #312: Add Task.memoizeOnSuccess and Coeval.memoizeOnSuccess
  • Issue #313: Add Task.deferFutureAction builder
  • Issue #325: Add SingleAssignmentCancelable.plusOne
  • Issue #319: Move and redesign exceptions in monix.execution
  • Issue #314: Task.sequence should have lazy behavior in evaluating the given sequence

Bug fixes:

  • Bug #268: Optimise the unsubscribe logic in PublishSubject
  • Bug #308: Fix NPE on AsyncSubject.unsubscribe
  • Bug #315: The MapTaskObservable internal object is exposed (ScalaDoc)
  • Bug #321: Observable.concatMap and mapTask cannot be canceled if the source has already completed
  • Documentation fixes: #307, #309, #311, #316 and #317

Build:

  • enabled the Scala Migration Manager (MiMa) in build.sbt to check for backwards compatibility problems
  • Issue #322: Switch projects which use CrossVersion.full/"org.scala-lang" to CrossVersion.patch/scalaOrganization.value

Upgrading #

To use the new version, include this in your build.sbt (and use %%% for Scala.js):

libraryDependencies += "io.monix" %% "monix" % "2.2.2"

The other projects from the @Monix organization have also been upgraded to depend on this new version.

shade, the Scala Memcached client:

dependencies += "io.monix" %% "shade" % "1.9.2"

monix-kafka, the Apache Kafka integration:

// For Kafka 8
libraryDependencies += "io.monix" %% "monix-kafka-8" % "0.11"

// For Kafka 9
libraryDependencies += "io.monix" %% "monix-kafka-9" % "0.11"

// For Kafka 10
libraryDependencies += "io.monix" %% "monix-kafka-10" % "0.11"

Enjoy!