com.fireflysource.common.io.Nio.kt Maven / Gradle / Ivy
The newest version!
@file:Suppress("BlockingMethodInNonBlockingContext", "KDocUnresolvedReference")
package com.fireflysource.common.io
import com.fireflysource.common.coroutine.CoroutineDispatchers.ioBlockingThreadPool
import com.fireflysource.common.coroutine.blocking
import com.fireflysource.common.coroutine.blockingAsync
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.future.await
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.io.Closeable
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.nio.channels.*
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.TimeUnit
/**
* Performs [AsynchronousFileChannel.lock] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousFileChannel.lockAwait() = suspendCancellableCoroutine { cont ->
lock(cont, asyncIOHandler())
closeOnCancel(cont)
}
/**
* Performs [AsynchronousFileChannel.lock] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousFileChannel.lockAwait(
position: Long,
size: Long,
shared: Boolean
) = suspendCancellableCoroutine { cont ->
lock(position, size, shared, cont, asyncIOHandler())
closeOnCancel(cont)
}
suspend fun T.useAwait(block: suspend (T) -> R): R {
try {
return block(this)
} catch (e: Throwable) {
throw e
} finally {
withContext(NonCancellable) {
this@useAwait?.closeAsync()?.join()
}
}
}
suspend fun T.useAwait(block: suspend (T) -> R): R {
try {
return block(this)
} catch (e: Throwable) {
throw e
} finally {
withContext(NonCancellable) {
this@useAwait?.closeAsync()?.await()
}
}
}
/**
* Close in the I/O blocking coroutine dispatcher
*/
fun Closeable.closeAsync() = blocking {
close()
}
fun openFileChannelAsync(file: Path, vararg options: OpenOption) = blockingAsync {
AsynchronousFileChannel.open(file, setOf(*options), ioBlockingThreadPool)
}
fun openFileChannelAsync(file: Path, options: Set) = blockingAsync {
AsynchronousFileChannel.open(file, options, ioBlockingThreadPool)
}
fun listFilesAsync(dir: Path) = blockingAsync {
Files.list(dir)
}
fun readFileLinesAsync(file: Path, charset: Charset = StandardCharsets.UTF_8) = blockingAsync {
Files.readAllLines(file, charset)
}
fun readFileBytesAsync(file: Path) = blockingAsync {
Files.readAllBytes(file)
}
fun deleteIfExistsAsync(file: Path) = blockingAsync {
Files.deleteIfExists(file)
}
fun existsAsync(file: Path, vararg options: LinkOption) = blockingAsync {
Files.exists(file, *options)
}
fun readAttributesAsync(file: Path, vararg options: LinkOption) = blockingAsync {
Files.readAttributes(file, BasicFileAttributes::class.java, *options)
}
fun writeFileLinesAsync(file: Path, iterable: Iterable, vararg options: OpenOption) = blockingAsync {
Files.write(file, iterable, *options)
}
fun writeFileBytesAsync(file: Path, byteArray: ByteArray, vararg options: OpenOption) = blockingAsync {
Files.write(file, byteArray, *options)
}
/**
* Performs [AsynchronousFileChannel.read] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousFileChannel.readAwait(
buf: ByteBuffer,
position: Long
) = suspendCancellableCoroutine { cont ->
read(buf, position, cont, asyncIOHandler())
closeOnCancel(cont)
}
/**
* Performs [AsynchronousFileChannel.write] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousFileChannel.writeAwait(
buf: ByteBuffer,
position: Long
) = suspendCancellableCoroutine { cont ->
write(buf, position, cont, asyncIOHandler())
closeOnCancel(cont)
}
/**
* Performs [AsynchronousServerSocketChannel.accept] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousServerSocketChannel.acceptAwait() =
suspendCancellableCoroutine { cont ->
accept(cont, asyncIOHandler())
closeOnCancel(cont)
}
/**
* Performs [AsynchronousSocketChannel.connect] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousSocketChannel.connectAwait(
socketAddress: SocketAddress
) = suspendCancellableCoroutine { cont ->
connect(socketAddress, cont, AsyncVoidIOHandler)
closeOnCancel(cont)
}
/**
* Performs [AsynchronousSocketChannel.read] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousSocketChannel.readAwait(
buf: ByteBuffer,
timeout: Long = 0L,
timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
read(buf, timeout, timeUnit, cont, asyncIOHandler())
closeOnCancel(cont)
}
suspend fun AsynchronousSocketChannel.readAwait(
buffers: Array,
offset: Int,
length: Int,
timeout: Long = 0L,
timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
read(buffers, offset, length, timeout, timeUnit, cont, asyncIOHandler())
closeOnCancel(cont)
}
/**
* Performs [AsynchronousSocketChannel.write] without blocking a thread and resumes when asynchronous operation completes.
* This suspending function is cancellable.
* If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
* *closes the underlying channel* and immediately resumes with [CancellationException].
*/
suspend fun AsynchronousSocketChannel.writeAwait(
buf: ByteBuffer,
timeout: Long = 0L,
timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
write(buf, timeout, timeUnit, cont, asyncIOHandler())
closeOnCancel(cont)
}
suspend fun AsynchronousSocketChannel.writeAwait(
buffers: Array,
offset: Int,
length: Int,
timeout: Long = 0L,
timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
write(buffers, offset, length, timeout, timeUnit, cont, asyncIOHandler())
closeOnCancel(cont)
}
// ---------------- private details ----------------
private fun Channel.closeOnCancel(cont: CancellableContinuation<*>) {
cont.invokeOnCancellation {
try {
close()
} catch (ex: Throwable) {
// Specification says that it is Ok to call it any time, but reality is different,
// so we have just to ignore exception
}
}
}
@Suppress("UNCHECKED_CAST")
private fun asyncIOHandler(): CompletionHandler> =
AsyncIOHandlerAny as CompletionHandler>
private object AsyncIOHandlerAny : CompletionHandler> {
override fun completed(result: Any, cont: CancellableContinuation) {
cont.resumeWith(Result.success(result))
}
override fun failed(ex: Throwable, cont: CancellableContinuation) {
// just return if already cancelled and got an expected exception for that case
if (ex is AsynchronousCloseException && cont.isCancelled) {
return
}
cont.resumeWith(Result.failure(ex))
}
}
private object AsyncVoidIOHandler : CompletionHandler> {
override fun completed(result: Void?, cont: CancellableContinuation) {
cont.resumeWith(Result.success(Unit))
}
override fun failed(ex: Throwable, cont: CancellableContinuation) {
// just return if already cancelled and got an expected exception for that case
if (ex is AsynchronousCloseException && cont.isCancelled) {
return
}
cont.resumeWith(Result.failure(ex))
}
}