├── .gitignore ├── .scalafmt.conf ├── build.sbt ├── core └── src │ └── main │ └── scala │ └── com │ └── softwaremill │ └── zio │ ├── HappyEyeballs.scala │ ├── ReleasableHappyEyeballs.scala │ ├── TestPrintln.scala │ └── TestSocket.scala ├── project ├── build.properties └── plugins.sbt └── version.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | .cache 5 | .history 6 | .lib/ 7 | dist/* 8 | target/ 9 | lib_managed/ 10 | src_managed/ 11 | project/boot/ 12 | project/plugins/project/ 13 | 14 | .idea* 15 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 2.5.3 2 | maxColumn = 140 3 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val commonSettings = commonSmlBuildSettings ++ Seq( 2 | organization := "com.softwaremill.zio", 3 | scalaVersion := "2.13.2" 4 | ) 5 | 6 | val scalaTest = "org.scalatest" %% "scalatest" % "3.1.0" % Test 7 | 8 | lazy val rootProject = (project in file(".")) 9 | .settings(commonSettings: _*) 10 | .settings(publishArtifact := false, name := "root") 11 | .aggregate(core) 12 | 13 | val zioVersion = "1.0.0-RC20" 14 | 15 | lazy val core: Project = (project in file("core")) 16 | .settings(commonSettings: _*) 17 | .settings( 18 | name := "core", 19 | libraryDependencies ++= Seq( 20 | "dev.zio" %% "zio" % zioVersion, 21 | scalaTest 22 | ) 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /core/src/main/scala/com/softwaremill/zio/HappyEyeballs.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill.zio 2 | 3 | import zio._ 4 | import zio.clock.Clock 5 | import zio.duration._ 6 | 7 | object HappyEyeballs { 8 | def apply[R, T](tasks: List[ZIO[R, Throwable, T]], delay: Duration): ZIO[R with Clock, Throwable, T] = tasks match { 9 | case Nil => IO.fail(new IllegalStateException("no tasks")) 10 | case task :: Nil => task 11 | case task :: otherTasks => 12 | Queue.bounded[Unit](1).flatMap { taskFailed => 13 | val taskWithSignalOnFailed = task.onError(_ => taskFailed.offer(())) 14 | val sleepOrFailed = ZIO.sleep(delay).race(taskFailed.take) 15 | 16 | taskWithSignalOnFailed.race(sleepOrFailed *> apply(otherTasks, delay)) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/scala/com/softwaremill/zio/ReleasableHappyEyeballs.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill.zio 2 | 3 | import zio.Exit.{Failure, Success} 4 | import zio._ 5 | import zio.clock.Clock 6 | import zio.duration._ 7 | 8 | object ReleasableHappyEyeballs { 9 | def apply[R, T]( 10 | tasks: List[ZIO[R, Throwable, T]], 11 | delay: Duration, 12 | releaseExtra: T => ZIO[R, Nothing, Unit] 13 | ): ZIO[R with Clock, Throwable, T] = 14 | for { 15 | successful <- Queue.bounded[T](tasks.size) 16 | enqueueingTasks = tasks.map { 17 | _.onExit { 18 | case Success(value) => successful.offer(value) 19 | case Failure(_) => ZIO.unit 20 | } 21 | } 22 | _ <- HappyEyeballs(enqueueingTasks, delay) 23 | // there has to be at least one, otherwise HE would fail 24 | first :: other <- successful.takeAll 25 | _ <- ZIO.foreach(other)(releaseExtra) 26 | } yield first 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/scala/com/softwaremill/zio/TestPrintln.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill.zio 2 | 3 | import zio._ 4 | import zio.clock.Clock 5 | import zio.duration._ 6 | 7 | object TestPrintln extends App { 8 | override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, ExitCode] = { 9 | val start = System.currentTimeMillis() 10 | def log(msg: String): UIO[Unit] = UIO { 11 | val now = System.currentTimeMillis() 12 | val second = (now - start) / 1000L 13 | println(s"after ${second}s: $msg") 14 | } 15 | 16 | def printSleepPrint(sleep: Duration, name: String): ZIO[Clock, Nothing, String] = 17 | log(s"START: $name") *> URIO.sleep(sleep) *> log(s"DONE: $name") *> UIO(name) 18 | 19 | def printSleepFail(sleep: Duration, name: String): ZIO[Clock, Throwable, String] = 20 | log(s"START: $name") *> URIO.sleep(sleep) *> log(s"FAIL: $name") *> IO.fail(new RuntimeException(s"FAIL: $name")) 21 | 22 | // 0s-6s: 1st should be interrupted 23 | // 2s-3s: 2nd should fail 24 | // 3s-6s: 3rd succeeds 25 | // 5s-6s: 4th should be interrupted 26 | // -----: 5th shouldn't start 27 | val result = HappyEyeballs( 28 | List( 29 | printSleepPrint(10.seconds, "task1"), 30 | printSleepFail(1.second, "task2"), 31 | printSleepPrint(3.seconds, "task3"), 32 | printSleepPrint(2.seconds, "task4"), 33 | printSleepPrint(2.seconds, "task5") 34 | ), 35 | 2.seconds 36 | ) 37 | 38 | result 39 | .tap(v => log(s"WON: $v")) 40 | .tapError(error => log(s"ERROR: $error")) 41 | .fold(_ => ExitCode.success, _ => ExitCode.failure) 42 | .untraced 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/scala/com/softwaremill/zio/TestSocket.scala: -------------------------------------------------------------------------------- 1 | package com.softwaremill.zio 2 | 3 | import zio._ 4 | import zio.duration._ 5 | import zio.blocking._ 6 | import zio.console._ 7 | import java.net.{InetAddress, Socket} 8 | 9 | object TestSocket extends App { 10 | override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, ExitCode] = { 11 | effectBlocking(InetAddress.getAllByName("debian.org").toList) 12 | .map { addresses => 13 | addresses.map(a => effectBlocking(new Socket(a, 443))) 14 | } 15 | .flatMap(tasks => ReleasableHappyEyeballs(tasks, 250.milliseconds, closeSocket)) 16 | .tap(v => putStrLn(s"Connected: ${v.getInetAddress}")) 17 | .tapError(error => putStrLn(s"ERROR: $error")) 18 | .fold(_ => ExitCode.success, _ => ExitCode.failure) 19 | } 20 | 21 | def closeSocket(s: Socket): ZIO[Blocking, Nothing, Unit] = effectBlocking(s.close()).catchAll(_ => ZIO.unit) 22 | } 23 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.3.12 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.5") 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.1-SNAPSHOT" 2 | --------------------------------------------------------------------------------