Im trying to alter ZIO's example code to fit what I want, but ran into a problem. I want to implement functionalities between different rpc calls, but I can't seem to get it to work, since in below example, only the while loop, rcpMethod3() and rcpMethod4() gets executed, whereas rcpMethod1() and rcpMethod2() doesn't.
I want to execute all of the rcpMethods and the while loop.
object Client extends App {
val clientLayer: Layer[Throwable, ClientZLayer] =
ClientZLayer.live(
ZManagedChannel(
ManagedChannelBuilder.forAddress("localhost", 8980).usePlaintext()
)
)
// rcp methods
def rcpMethod1(): ZIO[ClientZLayer with Console, Status, Unit] = {
for {
response <- ClientZLayer.rcpMethod1(Greeting("Hi"))
_ <- putStrLn(s"""Greeted: "${response.name}".""")
} yield ()
}
// Run the code
final def run(args: List[String]) = {
(for {
_ <- rcpMethod1()
_ <- rcpMethod2()
} yield ()).provideCustomLayer(clientLayer).exitCode
while(condition) {
// bla bla
}
(for {
_ <- rcpMethod3()
_ <- rcpMethod4()
} yield ()).provideCustomLayer(clientLayer).exitCode
}
}
The ZIO data type is a functional effect. A functional effect is a description of a workflow. This is why we have the run method at the end of the world. This run method executes the provided ZIO effect.
All the rcpMethodN methods are ZIO effect, so they are just a description of running workflow. If you want to run these effects sequentially you should compose them using for-comprehension or flatMap like this:
for {
_ <- rcpMethod1()
_ <- rcpMethod2()
_ <- rcpMethod3()
_ <- rcpMethod4()
} yield ()
The while(condition){ ... } is another mistake in your code. You should introduce these loop structs with ZIO data type. For example:
for {
_ <- rcpMethod1()
_ <- rcpMethod2()
_ <- ZIO.effect(???).repeatWhile(condition)
_ <- rcpMethod3()
_ <- rcpMethod4()
} yield ()
Related
I have a stream that just repeats every x seconds.
In my unit tests I want to test certain business logic, so I need my clock to start at 1:30pm and run until 1:45pm.
How can I mock this type of behaviour?
import java.time.{ZoneId, ZonedDateTime}
val zoneId = ZoneId.of("America/New_York")
Stream
.repeatEval {
for {
realTime <- Clock[F].realTimeInstant
zdt = ZonedDateTime.ofInstant(realTime, zoneId)
_ <- std.Console[F].println(s"zdt=$zdt")
} yield ()
}
You can use the TestControl structure from cats-effect-testkit to have fine-grained control over time in tests. It provides a full IORuntime, so your code just operates as normal
See the example of advancing the clock manually:
test("backoff appropriately between attempts") {
case object TestException extends RuntimeException
val action = IO.raiseError(TestException)
val program = Random.scalaUtilRandom[IO] flatMap { random =>
retry(action, 1.minute, 5, random)
}
TestControl.execute(program) flatMap { control =>
for {
_ <- control.results.assertEquals(None)
_ <- control.tick
_ <- 0.until(4) traverse { i =>
for {
_ <- control.results.assertEquals(None)
interval <- control.nextInterval
_ <- IO(assert(interval >= 0.nanos))
_ <- IO(assert(interval < (1 << i).minute))
_ <- control.advanceAndTick(interval)
} yield ()
}
_ <- control.results.assertEquals(Some(Outcome.failed(TestException)))
} yield ()
}
}
I am new to ZHub and ZStream and wanted to familiarize myself with their APIs.
Unfortnuately, I could not make this simple example work:
for
hub <- Hub.bounded[String](4)
stream = ZStream.fromHub(hub)
_ <- hub.publish("Hello")
_ <- hub.publish("World")
collected <- stream.runCollect
_ <- ZIO.foreach(collected) { msg => console.putStrLn(msg) }
yield
()
This program does not terminate, I suspect, because I am trying to collect an infinite stream. I have also tried to print the messages using stream.tap(...) or to shut down the hub. Nothing has helped.
What am I missing here? Any help is appreciated, thanks.
#adamgfraser kindly provided a working example on GitHub:
import zio._
import zio.stream._
object Example extends App {
def run(args: List[String]): URIO[ZEnv, ExitCode] =
for {
promise <- Promise.make[Nothing, Unit]
hub <- Hub.bounded[String](2)
stream = ZStream.managed(hub.subscribe).flatMap { queue =>
ZStream.fromEffect(promise.succeed(())) *>
ZStream.fromQueue(queue)
}
fiber <- stream.take(2).runCollect.fork
_ <- promise.await
_ <- hub.publish("Hello")
_ <- hub.publish("World")
collected <- fiber.join
_ <- ZIO.foreach(collected)(console.putStrLn(_)).orDie
} yield ExitCode.success
}
My mistake was to publish values to the hub before waiting for the subscription to complete.
help me how to organize a directory scan on ZIO. This is my version, but it doesn't track all file creation events (miss some events).
object Main extends App {
val program = for {
stream <- ZIO.succeed(waitEvents)
_ <- stream.run(ZSink.foreach(k => putStrLn(k.map(e => (e.kind(), e.context())).mkString("\n"))))
} yield ()
val managedWatchService = ZManaged.make {
for {
watchService <- FileSystem.default.newWatchService
path = Path("c:/temp")
_ <- path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE
)
} yield watchService
}(_.close.orDie)
val lookKey = ZManaged.make {
managedWatchService.use(watchService => watchService.take)
}(_.reset)
val waitEvents = ZStream.fromEffect {
lookKey.use(key => key.pollEvents)
}.repeat(Schedule.forever)
override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, ExitCode] =
program
.provideLayer(Console.live ++ Blocking.live ++ Clock.live)
.exitCode
}
Thank you for your advice.
You are forcing your WatchService to shutdown and recreate every time you poll for events. Since that probably involves some system handles it is likely fairly slow so you would probably missing file events that occur in between. More likely you want to produce the WatchService once and then poll it repeatedly. I would suggest something like this instead:
object Main extends App {
val managedWatchService = ZManaged.make {
for {
watchService <- FileSystem.default.newWatchService
path = Path("c:/temp")
_ <- path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE
)
} yield watchService
}(_.close.orDie)
// Convert ZManaged[R, E, ZStream[R, E, A]] into ZStream[R, E, A]
val waitEvents = ZStream.unwrapManaged(
managedWatchService.mapM(_.take).map { key =>
// Use simple effect composition instead of a managed for readability.
ZStream.repeatEffect(key.pollEvents <* key.reset)
// Optional: Flatten the `List` of values that is returned
.flattenIterables
}
)
val program = waitEvents
.map(e => (e.kind(), e.context()).toString)
.foreach(putStrLn).unit
override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, ExitCode] =
program
.provideLayer(Console.live ++ Blocking.live ++ Clock.live)
.exitCode
}
Also as a side note, when using ZManaged, you probably don't want to do
ZManaged.make(otherManaged.use(doSomething))(tearDown)
because you will cause the finalizers to execute out of order. ZManaged can already handle the ordering of teardown just through normal flatMap composition.
otherManaged.flatMap { other => ZManaged.make(doSomething(other))(tearDown) }
I expected that canceling Fiber makes it joinable. Here is an example of what I mean:
object TestFiber extends App {
implicit val contextShift: ContextShift[IO] =
IO.contextShift(ExecutionContext.global)
implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
val test = for {
fiber <- IO.never.attempt.start
_ <- fiber.cancel
_ <- fiber.join
_ <- IO(println("Finished"))
} yield ()
test.unsafeRunSync() //blocks instead of printing "Finished" and exit
}
I expected that after cancel, join would immediately return and Finished would be printed.
But the actual behavior is that the program hangs up. How to make a Fiber finished so it can be joinable?
This seems to be the intended, documented behaviour of cats-effect. The ZIO equivalent to cancel is interrupt, and it returns an Exit type that will allow you to handle all possible cases (success, error, interruption) very easily, so if possible, I'd encourage you to just switch to ZIO.
But if you need to use cats-effect, the best that I could come up with (and it's not very good) is to use a Deferred:
def catchCancel[F[_]: Concurrent, A](io: F[A]): F[(F[Unit], F[Either[Option[Throwable], A]])] =
for {
deferred <- Deferred[F, Either[Option[Throwable], A]]
fiber <- (for {
a <- Concurrent[F].unit.bracketCase(_ => io) {
case (_, ExitCase.Completed) => Concurrent[F].unit
case (_, ExitCase.Error(e)) => deferred.complete(Left(Some(e))).as(())
case (_, ExitCase.Canceled) => deferred.complete(Left(None))
}
_ <- deferred.complete(Right(a))
} yield ()).start
} yield (fiber.cancel, deferred.get)
And you can use it like so:
val test = for {
x <- catchCancel[IO, Nothing](IO.never)
(cancel, getResult) = x
_ <- cancel
result <- getResult
_ <- IO(println(s"Finished $result"))
} yield ()
But seriously, why would you inflict this onto yourself? Just switch to ZIO, it fixes this problem and a boatload of others.
My service layer is returning Futures.
I have this mess currently:
userService.getAll().map { users =>
for(u <- users) {
groupService.getByUserId(u.id).map {
blockingService.call(u)
otherService.bar(u.id).map {
lastService.foo(u.id)
}
}
}
}
Can I clean this up somehow? It has too many embedded map calls.
I am also scared that if in the future I refactor my code such that blockingService.call becauses Future based, my code will have unexpected problems potentially, so better to treat it as a Future?
Replace the nested maps with a for comprehension:
def doSomethingWithUser(u: User) = {
for {
_ <- groupService.getByUserId(u.id)
_ <- Future(blockingService.call(u)) // Notice Future.apply here.
_ <- otherService.bar(u.id)
result <- lastService.foo(u.id)
} yield result
}
for {
users <- userService.getAll()
results <- Future.traverse(users)(doSomethingWithUser) // Notice Future.traverse here.
} yield results