diff --git a/README.md b/README.md index 342ae8c..6d690bf 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Different platform-specific implementations are provided. | [() -> ReadableByteChannel] Factory | JVM, Android | JVM, Android | | [AsynchronousFileChannel] | JVM, Android | JVM, Android | + +> [!IMPORTANT] +> For using [AsynchronousFileChannel], you need to add Kotlin Coroutines dependency to your project. + [RandomAccessData]: fluxo-io-rad/src/commonMain/kotlin/fluxo/io/rad/RandomAccessData.common.kt#L29 [ByteArray]: fluxo-io-rad/src/commonMain/kotlin/fluxo/io/rad/RadByteArrayAccessor.kt#L21 diff --git a/fluxo-io-rad/api/android/fluxo-io-rad.api b/fluxo-io-rad/api/android/fluxo-io-rad.api index d3b6b63..4248c5f 100644 --- a/fluxo-io-rad/api/android/fluxo-io-rad.api +++ b/fluxo-io-rad/api/android/fluxo-io-rad.api @@ -4,10 +4,11 @@ public final class fluxo/io/FluxoIoLogger { public abstract class fluxo/io/SharedCloseable : java/io/Closeable { public fun ()V + public final fun addOnSharedCloseListener (Lkotlin/jvm/functions/Function1;)V public final fun close ()V public final fun isOpen ()Z protected abstract fun onSharedClose ()V - public final fun onSharedClose (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; + public final fun removeOnSharedCloseListener (Lkotlin/jvm/functions/Function1;)V public final fun retain ()V } diff --git a/fluxo-io-rad/api/fluxo-io-rad.klib.api b/fluxo-io-rad/api/fluxo-io-rad.klib.api index 3252bf7..0084926 100644 --- a/fluxo-io-rad/api/fluxo-io-rad.klib.api +++ b/fluxo-io-rad/api/fluxo-io-rad.klib.api @@ -26,8 +26,9 @@ abstract class fluxo.io/SharedCloseable : kotlin/AutoCloseable { // fluxo.io/Sha final fun (): kotlin/Boolean // fluxo.io/SharedCloseable.isOpen.|(){}[0] abstract fun onSharedClose() // fluxo.io/SharedCloseable.onSharedClose|onSharedClose(){}[0] + final fun addOnSharedCloseListener(kotlin/Function1) // fluxo.io/SharedCloseable.addOnSharedCloseListener|addOnSharedCloseListener(kotlin.Function1){}[0] final fun close() // fluxo.io/SharedCloseable.close|close(){}[0] - final fun onSharedClose(kotlin/Function1): kotlinx.coroutines/DisposableHandle // fluxo.io/SharedCloseable.onSharedClose|onSharedClose(kotlin.Function1){}[0] + final fun removeOnSharedCloseListener(kotlin/Function1) // fluxo.io/SharedCloseable.removeOnSharedCloseListener|removeOnSharedCloseListener(kotlin.Function1){}[0] final fun retain() // fluxo.io/SharedCloseable.retain|retain(){}[0] } diff --git a/fluxo-io-rad/api/jvm/fluxo-io-rad.api b/fluxo-io-rad/api/jvm/fluxo-io-rad.api index d3b6b63..4248c5f 100644 --- a/fluxo-io-rad/api/jvm/fluxo-io-rad.api +++ b/fluxo-io-rad/api/jvm/fluxo-io-rad.api @@ -4,10 +4,11 @@ public final class fluxo/io/FluxoIoLogger { public abstract class fluxo/io/SharedCloseable : java/io/Closeable { public fun ()V + public final fun addOnSharedCloseListener (Lkotlin/jvm/functions/Function1;)V public final fun close ()V public final fun isOpen ()Z protected abstract fun onSharedClose ()V - public final fun onSharedClose (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; + public final fun removeOnSharedCloseListener (Lkotlin/jvm/functions/Function1;)V public final fun retain ()V } diff --git a/fluxo-io-rad/build.gradle.kts b/fluxo-io-rad/build.gradle.kts index ee7fa31..a2644e8 100644 --- a/fluxo-io-rad/build.gradle.kts +++ b/fluxo-io-rad/build.gradle.kts @@ -23,26 +23,24 @@ fkcSetupMultiplatform( }, ) { common.main.dependencies { - implementation(libs.coroutines) // implementation(libs.kotlinx.io.core) } - val commonJvm = commonJvm commonJvm.main.dependencies { compileOnly(rootProject.extra["androidJar"]!!) compileOnly(libs.androidx.annotation) compileOnly(libs.jetbrains.annotation) + compileOnly(libs.coroutines) } val commonJs = commonJs commonJs.main.dependencies { - api(libs.kotlinx.atomicfu) + // Fix JS build KLIB issue + implementation(libs.kotlinx.atomicfu) } - arrayOf(commonJvm, commonApple, commonJs, commonLinux, commonMingw).forEach { - it.main.dependencies { - // implementation(libs.okio) - } + commonNative.main.dependencies { + implementation(libs.stately.concurrent.collections) } } diff --git a/fluxo-io-rad/dependencies/androidDebugRuntimeClasspath.txt b/fluxo-io-rad/dependencies/androidDebugRuntimeClasspath.txt index 8b9c5ce..52f513f 100644 --- a/fluxo-io-rad/dependencies/androidDebugRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/androidDebugRuntimeClasspath.txt @@ -1,5 +1 @@ org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC -org.jetbrains:annotations:23.0.0 diff --git a/fluxo-io-rad/dependencies/androidReleaseRuntimeClasspath.txt b/fluxo-io-rad/dependencies/androidReleaseRuntimeClasspath.txt index 8b9c5ce..52f513f 100644 --- a/fluxo-io-rad/dependencies/androidReleaseRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/androidReleaseRuntimeClasspath.txt @@ -1,5 +1 @@ org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC -org.jetbrains:annotations:23.0.0 diff --git a/fluxo-io-rad/dependencies/debugRuntimeClasspath.txt b/fluxo-io-rad/dependencies/debugRuntimeClasspath.txt index 81aefab..52f513f 100644 --- a/fluxo-io-rad/dependencies/debugRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/debugRuntimeClasspath.txt @@ -1,5 +1 @@ org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC -org.jetbrains:annotations:24.1.0 diff --git a/fluxo-io-rad/dependencies/jsCompileClasspath.txt b/fluxo-io-rad/dependencies/jsCompileClasspath.txt index 342e0ae..da28072 100644 --- a/fluxo-io-rad/dependencies/jsCompileClasspath.txt +++ b/fluxo-io-rad/dependencies/jsCompileClasspath.txt @@ -4,5 +4,3 @@ org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 org.jetbrains.kotlin:kotlinx-atomicfu-runtime:2.0.20-Beta1 org.jetbrains.kotlinx:atomicfu-js:0.25.0 org.jetbrains.kotlinx:atomicfu:0.25.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC diff --git a/fluxo-io-rad/dependencies/jsRuntimeClasspath.txt b/fluxo-io-rad/dependencies/jsRuntimeClasspath.txt index 342e0ae..17694b2 100644 --- a/fluxo-io-rad/dependencies/jsRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/jsRuntimeClasspath.txt @@ -2,7 +2,3 @@ org.jetbrains.kotlin:kotlin-dom-api-compat:2.0.20-Beta1 org.jetbrains.kotlin:kotlin-stdlib-js:2.0.20-Beta1 org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 org.jetbrains.kotlin:kotlinx-atomicfu-runtime:2.0.20-Beta1 -org.jetbrains.kotlinx:atomicfu-js:0.25.0 -org.jetbrains.kotlinx:atomicfu:0.25.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC diff --git a/fluxo-io-rad/dependencies/jvmRuntimeClasspath.txt b/fluxo-io-rad/dependencies/jvmRuntimeClasspath.txt index 8b9c5ce..52f513f 100644 --- a/fluxo-io-rad/dependencies/jvmRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/jvmRuntimeClasspath.txt @@ -1,5 +1 @@ org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC -org.jetbrains:annotations:23.0.0 diff --git a/fluxo-io-rad/dependencies/releaseRuntimeClasspath.txt b/fluxo-io-rad/dependencies/releaseRuntimeClasspath.txt index 81aefab..52f513f 100644 --- a/fluxo-io-rad/dependencies/releaseRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/releaseRuntimeClasspath.txt @@ -1,5 +1 @@ org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC -org.jetbrains:annotations:24.1.0 diff --git a/fluxo-io-rad/dependencies/wasmJsCompileClasspath.txt b/fluxo-io-rad/dependencies/wasmJsCompileClasspath.txt index b7c3e12..464ac5c 100644 --- a/fluxo-io-rad/dependencies/wasmJsCompileClasspath.txt +++ b/fluxo-io-rad/dependencies/wasmJsCompileClasspath.txt @@ -2,5 +2,3 @@ org.jetbrains.kotlin:kotlin-stdlib-wasm-js:2.0.20-Beta1 org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 org.jetbrains.kotlinx:atomicfu-wasm-js:0.25.0 org.jetbrains.kotlinx:atomicfu:0.25.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC diff --git a/fluxo-io-rad/dependencies/wasmJsRuntimeClasspath.txt b/fluxo-io-rad/dependencies/wasmJsRuntimeClasspath.txt index b7c3e12..464ac5c 100644 --- a/fluxo-io-rad/dependencies/wasmJsRuntimeClasspath.txt +++ b/fluxo-io-rad/dependencies/wasmJsRuntimeClasspath.txt @@ -2,5 +2,3 @@ org.jetbrains.kotlin:kotlin-stdlib-wasm-js:2.0.20-Beta1 org.jetbrains.kotlin:kotlin-stdlib:2.0.20-Beta1 org.jetbrains.kotlinx:atomicfu-wasm-js:0.25.0 org.jetbrains.kotlinx:atomicfu:0.25.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-wasm-js:1.9.0-RC -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC diff --git a/fluxo-io-rad/src/commonJsMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonJs.kt b/fluxo-io-rad/src/commonJsMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonJs.kt new file mode 100644 index 0000000..d6e0b7c --- /dev/null +++ b/fluxo-io-rad/src/commonJsMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonJs.kt @@ -0,0 +1,6 @@ +@file:Suppress("FunctionName") + +package fluxo.io.util + +// WASM and JS are single-threaded, we can use a regular HashMap. +internal actual fun ConcurrentHashMap(): MutableMap = HashMap() diff --git a/fluxo-io-rad/src/commonJvmMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonJvm.kt b/fluxo-io-rad/src/commonJvmMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonJvm.kt new file mode 100644 index 0000000..9b7b004 --- /dev/null +++ b/fluxo-io-rad/src/commonJvmMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonJvm.kt @@ -0,0 +1,6 @@ +@file:Suppress("FunctionName") + +package fluxo.io.util + +internal actual fun ConcurrentHashMap(): MutableMap = + java.util.concurrent.ConcurrentHashMap() diff --git a/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/SharedCloseable.kt b/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/SharedCloseable.kt index d4ca9df..d65f865 100644 --- a/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/SharedCloseable.kt +++ b/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/SharedCloseable.kt @@ -3,11 +3,8 @@ package fluxo.io import fluxo.io.internal.ThreadSafe +import fluxo.io.util.ConcurrentHashMap import kotlinx.atomicfu.atomic -import kotlinx.coroutines.CompletableJob -import kotlinx.coroutines.CompletionHandler -import kotlinx.coroutines.DisposableHandle -import kotlinx.coroutines.Job /** * A [SharedCloseable] is a resource that can be shared between multiple consumers. @@ -23,19 +20,45 @@ public abstract class SharedCloseable : Closeable { public val isOpen: Boolean get() = retainsCount.value > 0 - // Job is used to handle shared close listeners. - private val job: CompletableJob = Job() + + private val sharedCloseListeners = ConcurrentHashMap<(cause: Throwable?) -> Unit, Boolean>() + + public fun addOnSharedCloseListener(cb: (cause: Throwable?) -> Unit) { + sharedCloseListeners[cb] = true + } + + public fun removeOnSharedCloseListener(cb: (cause: Throwable?) -> Unit) { + sharedCloseListeners.remove(cb) + } + public final override fun close() { - if (retainsCount.decrementAndGet() == 0) { - @Suppress("TooGenericExceptionCaught") - try { - onSharedClose() - job.complete() - } catch (e: Throwable) { - job.completeExceptionally(e) - throw e + if (retainsCount.decrementAndGet() != 0) { + return + } + + @Suppress("TooGenericExceptionCaught") + try { + onSharedClose() + notifyListenersOnce(e = null) + } catch (e: Throwable) { + while (true) { + try { + notifyListenersOnce(e) + break + } catch (e2: Throwable) { + e.addSuppressed(e2) + } } + throw e + } + } + + private fun notifyListenersOnce(e: Throwable?) { + val iterator = sharedCloseListeners.keys.iterator() + for (listener in iterator) { + iterator.remove() + listener(e) } } @@ -49,8 +72,6 @@ public abstract class SharedCloseable : Closeable { @Throws(IOException::class) protected abstract fun onSharedClose() - public fun onSharedClose(cb: CompletionHandler): DisposableHandle = - job.invokeOnCompletion(cb) public fun retain() { while (true) { diff --git a/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/util/ConcurrentHashMap.common.kt b/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/util/ConcurrentHashMap.common.kt new file mode 100644 index 0000000..de36b68 --- /dev/null +++ b/fluxo-io-rad/src/commonMain/kotlin/fluxo/io/util/ConcurrentHashMap.common.kt @@ -0,0 +1,5 @@ +@file:Suppress("FunctionName") + +package fluxo.io.util + +internal expect fun ConcurrentHashMap(): MutableMap diff --git a/fluxo-io-rad/src/nativeMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonNative.kt b/fluxo-io-rad/src/nativeMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonNative.kt new file mode 100644 index 0000000..cdb2ce1 --- /dev/null +++ b/fluxo-io-rad/src/nativeMain/kotlin/fluxo/io/util/ConcurrentHashMap.commonNative.kt @@ -0,0 +1,6 @@ +@file:Suppress("FunctionName") + +package fluxo.io.util + +internal actual fun ConcurrentHashMap(): MutableMap = + co.touchlab.stately.collections.ConcurrentMutableMap() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba25c0e..9d906f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -66,6 +66,8 @@ okio = { module = "com.squareup.okio:okio", version.ref = "okio" } kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" } kotlinx-io-bytestring = { module = "org.jetbrains.kotlinx:kotlinx-io-bytestring", version.ref = "kotlinx-io" } +stately-concurrent-collections = { module = "co.touchlab:stately-concurrent-collections", version = "2.0.7" } + # https://developer.android.com/jetpack/androidx/releases/annotation # https://mvnrepository.com/artifact/androidx.annotation/annotation androidx-annotation = { module = "androidx.annotation:annotation", version = "1.8.0" }