zio quick read: Extracting the executor for a service

zio quick read: Extracting the executor for a service

I had the need to obtain an Executor from the default zio runtime and use it to create a service.

Let’s assume you have a

def mkMyService(executor: java.util.concurrent.Executor): MyService = ???

and you want to create the client using the zio default runtime thread pool. For example, to use a specific Executor with HttpClient which is a new async http client in java 11. Otherwise, HttpClient uses a common, shared thread pool which may or may not be what you want.

Since the executor is in the ZIO environment and we want to create a layer so that the “client” is in the R (the input environment) for all effects, we can pull the executor out as an effect then add it to a layer.

Here’s the code (this is the hard way):

type MyEnv = Has[MyService]
val  resources: Layer[Nothing, MyEnv] = 
	ZLayer.fromEffect(ZIO.executor.map(_.asJava)) >>>
	ZLayer.requires[Has[java.util.concurrent.Executor]]
	.map(e => Has(mkService(e.get)))

The thinking is:

  • Pull the executor out using ZIO.executor effect. Then add the “service” using ZLayer.fromEffect.
  • Vertically push the executor “service” into the next layer using >>>.
  • Use ZLayer.requires to create a dependency on a layer service that has an Executor but map into it to create the final service we want.

We can also do this another way that’s easier.

Using ZLayer.fromService, we can have zio pull out the service. ZLayer.fromService has implicit constraints that say that Has[] services must exist. Since ZLayer.fromEffect creates a Has[] service from the ZIO.executor effect we have the input coming in from >>>. The output is “wrapped” automatically into a Has by zio.

val  resources: Layer[Nothing, MyEnv] = 
	ZLayer.fromEffect(ZIO.executor.map(_.asJava)) >>>
	ZLayer.fromService((e: java.util.concurrent.Executor) => mkService(e))
	
// even shorter with currying
val  resources: Layer[Nothing, MyEnv] = 
	ZLayer.fromEffect(ZIO.executor.map(_.asJava)) >>>
	ZLayer.fromService(mkService)

To use it, treat it like any other layer:

object Main extends zio.App:
  def run(args: List[String]) =
	program.provideCustomLayer(resources).exitCode

val program: ZIO[MyEnv, Nothing, Unit] = ???

It’s important to note that once you do this, all calls that may block the thread, say HttpClient.send vs HttpClient.sendAsync need to be enclosed in the proper blocking { ... } construct in ZIO. This will force the call to run on a special zio thread specifically reserved for blocking calls. This way, the main computation threads will not be blocked. Of course, if you are using the synchronous calls you may want to run them on the common thread pool executor anyway but that’s up to you.

That’s it!

Comments

Popular posts from this blog

zio layers and framework integration

typescript and react types

typescript ambient declarations, global.d.ts, lib and typeRoot.md