-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
One module, blocking & non-blocking operations, misc. (#385)
* Improve end-of-stream handling. As discussed in issue #247. * Update docs/essentials/index.md Improve wording. Co-authored-by: Maxim Schuwalow <16665913+mschuwalow@users.noreply.github.com> * Switch to using ZManaged for core resources. * Add resource management documentation. * Fix extension method on Scala 3. * Separate blocking and non-blocking APIs. The channel APIs (except for asyncrhonous channels) are now split into three parts: * Blocking (BlockingOps) * Non-blocking (NonBlockingOps) * Core (On the channel itself) Blocking and non-blocking APIs are the same in many cases (GatheringByteOps and ScatteringByteOps), but there are some differences. The blocking API usage is performed as a block in by using the `useBlocking` method on the channel. This method switches to the ZIO blocking thread pool and installs interrupt handling, which is necessary to interrupt blocking I/O calls if the ZIO fiber is interrupted. This imposes an overhead ever time it is needed, this approach aims to pay the cost once per channel, rather than once per read or write. Non-blocking usage does not require the special setup required for blocking, but for consistency the API is accessed the same way, via the `useNonBlocking` method on the channel. * Improvements to WatchService. * Add utility methods and example for directory watching. * Improvements to Files. * Use new ZStream adaptors for Java iterators and streams. * Use the ZIO-NIO Charset wrapper * Implement `lines` method * Implement `copy` method * Move Files object into core module. * Add streaming read and write. * Add example stream-based TCP server. * Improve InetSocketAddress and socket binding APIs. Use meaningful names for the InetSocketAddress constructors. Make binding to an automatically assigned socket address explicit. * Test that blocking read/write/accept can be interrupted. * Remove non-core project. * Rename nio-core project to nio. * Rename zio.nio.core package to zio.nio. * Make Selector#select interruptible. * Fix SelectableChannel#register error type. * Improve `SelectableChannel#register` API. * Improve InetSocketAddress API. * Don't hold two references to underlying Java buffer. * Improvements to address handling. * Add localhost constructor to InetSocketAddress. * Improvements to async channels. Make AsynchronousByteChannel callbacks interruptible by closing the channel on interruption. Looking at the JVM implementation, it seems async channel callbacks only ever return IOException (not surprising) so tighten the error types to IOException. Add some socket channel write variants that were missing. * Add stream/sink to AsynchronousFileChannel. Co-authored-by: Lachlan O'Dea <lodea@mac.com>
- Loading branch information
1 parent
21661e1
commit 04361c7
Showing
89 changed files
with
2,665 additions
and
2,956 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
--- | ||
id: essentials_blocking | ||
title: "Blocking I/O" | ||
--- | ||
|
||
The default ZIO runtime assumes that threads will never block, and maintains a small fixed-size thread pool to perform all its operations. If threads become blocked, CPU utilization can be reduced as the number of available threads drops below the number of available CPU cores. If enough threads block, the entire program may halt. | ||
|
||
Another issue with blocked threads is interruption. It is important that if the ZIO fiber is interrupted that this cancels the blocking operation and unblocks the thread. | ||
|
||
Many NIO operations can block the calling thread when called. ZIO-NIO provides APIs to help ZIO-based code deal with this. The following describes how to use channels that offer blocking operations, which is all channels except for the asynchronous ones. | ||
|
||
## Blocking and Non-Blocking Channel Operations | ||
|
||
Channel APIs that may block are not exposed on the channel itself. They are accessed via the channel's `useBlocking` method. You provide this method a function that excepts a `BlockingOps` object and returns a `ZIO` effect value. The `BlockingOps` parameter will be appropriate to the type of channel and has the actual blocking I/O effects such as read and write. | ||
|
||
The `useBlocking` method performs some setup required for safe use of blocking NIO APIs: | ||
|
||
* Puts the channel in blocking mode | ||
* Runs the resulting effect value on ZIO's blocking thread pool, leaving the standard pool unblocked. | ||
* Installs interrupt handling, so the channel will be closed if the ZIO fiber is interrupted. This unblocks the blocked I/O operation. (Note that NIO does not offer a way to interrupt a blocked I/O operation on a channel that does not close the channel). | ||
|
||
Non-blocking usage does not require this special handling, but for consistency the non-blocking operations are accessed in a similar way by calling `useNonBlocking` on the channel. For some channels there are some small differences between the blocking and non-blocking APIs. For example, `SocketChannel` only offers the `finishConnect` operation in the non-blocking case, as it is never needed in blocking mode. | ||
|
||
```scala mdoc:silent | ||
import zio.ZIO | ||
import zio.nio._ | ||
import zio.nio.channels._ | ||
|
||
def readHeader(c: SocketChannel): ZIO[Blocking, IOException, (Chunk[Byte], Chunk[Byte])] = | ||
c.useBlocking { ops => | ||
ops.readChunk(10) <*> ops.readChunk(25) | ||
} | ||
``` | ||
|
||
### Using Managed Channels | ||
|
||
To help with the common use-case where you want to create a channel, there is versions of `useBlocking` and `useNonBlocking` that can be called directly on a managed value providing a channel. | ||
|
||
`useNioBlocking` provides both the channel and the requested type of operations: | ||
|
||
```scala mdoc:silent | ||
import zio.nio._ | ||
import zio.nio.channels._ | ||
|
||
SocketChannel.open.useNioBlocking { (channel, blockingOps) => | ||
blockingOps.readChunk(100) <*> channel.remoteAddress | ||
} | ||
``` | ||
|
||
If you don't need the channel, there's `useNioBlockingOps`: | ||
|
||
```scala mdoc:silent | ||
import zio.nio.channels._ | ||
|
||
SocketChannel.open.useNioBlockingOps { blockingOps => | ||
blockingOps.readChunk(100) | ||
} | ||
``` | ||
|
||
To use the channel in non-blocking mode, there's corresponding `useNioNonBlocking` and `useNioNonBlockingOps` methods. | ||
|
||
### Avoiding Asynchronous Boundaries | ||
|
||
If you have a complex program that makes more than one call to `useBlocking`, then it may be worth running *all* of the ZIO-NIO parts using the blocking pool. This can be done by wrapping the effect value with your ZIO-NIO operations in `zio.blocking.blocking`. | ||
|
||
If this isn't done, you can end up with the calls using `BlockingOps` running on a thread from the blocking pool, while the other parts run on a thread from the standard pool. This involves an "asynchronous boundary" whever the fiber changes the underlying thread it's running on, which imposes some overheads including a full memory barrier. By using `zio.blocking.blocking` up-front, all the code can run on the same thread from the blocking pool. | ||
|
||
## Comparing the Channel Options | ||
|
||
There are three main styles of channel available: blocking, non-blocking and asynchronous. Which to choose? | ||
|
||
### Blocking Channels | ||
|
||
Easy to use, with a straight-forward operation. The downsides are that you have to use `useBlocking`, which creates a new thread, and will create an additional thread for every forked fiber subsequently created. Essentially you have a blocked thread for every active I/O call, which limits scalability. Also, the additional interrupt handling logic imposes a small overhead. | ||
|
||
### Non-Blocking Channels | ||
|
||
These scale very well because you basically do as many concurrent I/O operations as you like without creating any new threads. The big downside is that they aren't of practical use without using a `Selector`, which is *very* tricky API to use correctly. | ||
|
||
Note that while it is possible to use non-blocking channels without a `Selector`, this means you have to busy-wait on the channel for the simplest reads and writes. It's not efficient. | ||
|
||
The other issue is that only network channels and pipes support non-blocking mode. | ||
|
||
### Asynchronous Channels | ||
|
||
Asynchronous channels give us what we want: we don't need a `Selector` to use them, and our thread will never block when we use them. | ||
|
||
However, it should be noted that asynchronous file I/O is not currently possible on the JVM. `AsynchronousFileChannel` is performing blocking I/O using a pool of blocked threads, which exactly what `useBlocking` does, and shares the same drawbacks. It may be preferable to use a standard `FileChannel`, as you'll have more visibility and control over what's going on. | ||
|
||
The asynchronous socket channels do *appear* to use non-blocking I/O, although they also have some form of internal thread pool as well. These should scale roughly as well as non-blocking channels. One downside is that there is no asynchronous datagram channel. |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Oops, something went wrong.