Created
March 12, 2019 07:49
-
-
Save Yyukan/49da69b0e7cf8b97ffb5419d8140dc2b to your computer and use it in GitHub Desktop.
The Functional Scala Concurrency Challenge http://degoes.net/articles/zio-challenge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net | |
import net.Tap.Percentage | |
import scalaz.zio.{Ref, UIO, ZIO} | |
/** | |
* A `Tap` adjusts the flow of tasks through | |
* an external service in response to observed | |
* failures in the service, always trying to | |
* maximize flow while attempting to meet the | |
* user-defined upper bound on failures. | |
*/ | |
trait Tap[-E1, +E2] { | |
/** | |
* Sends the task through the tap. The | |
* returned task may fail immediately with a | |
* default error depending on the service | |
* being guarded by the tap. | |
*/ | |
def apply[R, E >: E2 <: E1, A](effect: ZIO[R, E, A]): ZIO[R, E, A] | |
} | |
class SmartTap[-E1, +E2](errBound : Percentage, | |
qualified : E1 => Boolean, | |
rejected : => E2, | |
state: Ref[Tap.State] | |
) extends Tap[E1, E2] { | |
override def apply[R, E >: E2 <: E1, A](effect: ZIO[R, E, A]): ZIO[R, E, A] = for { | |
_ <- state.update(f => f.copy(total = f.total + 1)) | |
s <- state.get | |
r <- if (s.failed / s.total * 100 > errBound) { | |
ZIO.fail(rejected) | |
} else { | |
run(effect) | |
} | |
} yield r | |
private def run[R, E >: E2 <: E1, A](effect: ZIO[R, E, A]): ZIO[R, E, A] = for { | |
f <- effect.fork | |
r <- f.join.catchSome { | |
case e if qualified(e) => | |
state.update(s => s.copy(failed = s.failed + 1)) *> effect | |
} | |
} yield r | |
} | |
object Tap { | |
type Percentage = Int | |
case class State(total: Double = 0, failed: Double = 0) | |
/** | |
* Creates a tap that aims for the specified | |
* maximum error rate, using the specified | |
* function to qualify errors (unqualified | |
* errors are not treated as failures for | |
* purposes of the tap), and the specified | |
* default error used for rejecting tasks | |
* submitted to the tap. | |
*/ | |
def make[E1, E2](errBound : Percentage, | |
qualified : E1 => Boolean, | |
rejected : => E2): UIO[Tap[E1, E2]] = | |
for { | |
state <- Ref.make[State](State()) | |
} yield | |
new SmartTap[E1, E2]( | |
errBound, | |
qualified, | |
rejected, | |
state) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment