From 69289d184dee309fa83a1faa0e2a66c05b4a4a6d Mon Sep 17 00:00:00 2001 From: William Walker <wnwalker@ucsc.edu> Date: Thu, 5 Dec 2024 08:40:29 -0800 Subject: [PATCH] Dispatcher coordination -Create limited library dispatcher and assign to injected HTTP client engines -Allow choosing main library dispatcher on a per-platform basis by adding dispatcher param to createDispatcher() utility fun --- .../kotlin/edu/ucsc/its/temerity/Actual.kt | 12 +++++++- .../kotlin/edu/ucsc/its/temerity/Expect.kt | 4 +-- .../its/temerity/core/DispatcherFactory.kt | 5 +++- .../edu/ucsc/its/temerity/core/Temerity.kt | 29 ++++++++++++------- .../kotlin/edu/ucsc/its/temerity/Actual.kt | 12 +++++++- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt index 5aac3ef..a15413e 100644 --- a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt +++ b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt @@ -17,9 +17,19 @@ */ package edu.ucsc.its.temerity +import edu.ucsc.its.temerity.core.DispatcherFactory.createDispatcher import io.ktor.client.engine.android.Android +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import org.koin.dsl.module internal actual fun platformModule() = module { - single { Android.create() } + single { (threadCount: Int, dispatcherName: String) -> + createDispatcher(Dispatchers.IO, threadCount, dispatcherName) + } + single { (libraryCoroutineDispatcher: CoroutineDispatcher) -> + Android.create { + dispatcher = libraryCoroutineDispatcher + } + } } diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt index 41bdf96..13146c7 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt @@ -20,8 +20,8 @@ package edu.ucsc.its.temerity import org.koin.core.module.Module /** - * This function provides an expectation for the implementation of a platform-specific dependency module + * This function provides an implementation of a platform-specific dependency module * Implementations for each source set contain factories for injected platform-specific dependencies that the Temerity library relies on. - * For now, this includes an HttpClientEngine for making requests, and a PreferencesSettings object for storing settings. + * For now, this includes an HttpClientEngine for making requests */ internal expect fun platformModule(): Module diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt index 088ae9d..ba2536d 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt @@ -19,12 +19,15 @@ package edu.ucsc.its.temerity.core import edu.ucsc.its.temerity.core.Temerity.Companion.DEFAULT_MINIMUM_THREAD_COUNT import edu.ucsc.its.temerity.createJobScope +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers internal object DispatcherFactory { - internal fun createLibraryScope(threadCount: Int): CoroutineScope = createJobScope(Dispatchers.IO.limitedParallelism(threadCount), allowIndependentFailure = true) + internal fun createDispatcher(dispatcherThreadPool: CoroutineDispatcher = Dispatchers.IO, threadCount: Int, dispatcherName: String): CoroutineDispatcher = dispatcherThreadPool.limitedParallelism(threadCount, dispatcherName) + + internal fun createLibraryScope(dispatcher: CoroutineDispatcher): CoroutineScope = createJobScope(dispatcher, allowIndependentFailure = true) private fun availableThreads() = Runtime.getRuntime().availableProcessors().minus(1) diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt index d732782..935c401 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt @@ -37,6 +37,7 @@ import edu.ucsc.its.temerity.TemClientConfig import edu.ucsc.its.temerity.TemerityApi import edu.ucsc.its.temerity.api.PlatformApi import edu.ucsc.its.temerity.api.createPlatformApi +import edu.ucsc.its.temerity.core.DispatcherFactory.createDispatcher import edu.ucsc.its.temerity.core.DispatcherFactory.createLibraryScope import edu.ucsc.its.temerity.core.JsonFactory.buildJson import edu.ucsc.its.temerity.core.Temerity.Companion.DEFAULT_WEB_TIMEOUT @@ -66,6 +67,7 @@ import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.header import io.ktor.client.statement.HttpResponse +import io.ktor.client.utils.clientDispatcher import io.ktor.http.URLProtocol import io.ktor.util.cio.toByteArray import io.ktor.utils.io.ByteReadChannel @@ -97,6 +99,8 @@ import org.koin.dsl.module import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import co.touchlab.kermit.Logger as KermitLogger import io.ktor.client.plugins.logging.Logger as KtorLogger @@ -118,7 +122,7 @@ public class Temerity internal constructor( // TODO: Use this as cache4k expiration time public val DEFAULT_CACHE_EXPIRATION: Duration = 15.minutes internal const val DEFAULT_MINIMUM_THREAD_COUNT: Int = 2 - + internal fun createLogger(tag: String?, supportKtxNotebook: Boolean = false): co.touchlab.kermit.Logger = co.touchlab.kermit.Logger( config = loggerConfigInit( @@ -136,7 +140,7 @@ public class Temerity internal constructor( internal val libModule = module { factory { (config: TemClientConfig, kermit: co.touchlab.kermit.Logger) -> buildHttpClient( - httpClientEngine = get(), + httpClientEngine = get( parameters = { parametersOf(libraryCoroutineDispatcher) }), config = config, logger = kermit, ) @@ -150,8 +154,8 @@ public class Temerity internal constructor( } ktorfit.createPlatformApi() } - single { (threadCount: Int) -> - createLibraryScope(threadCount) + single { (dispatcher: CoroutineDispatcher) -> + createLibraryScope(dispatcher) } } @@ -161,7 +165,7 @@ public class Temerity internal constructor( * via factories defined as part of the lib Module. */ private fun createKoinApp() = object : TemerityKoinContext() { - val logger = createLogger("TemerityLib") + val logger = createLogger("TemerityLib Koin DI Context") override val koinApp = koinApplication { logger(object : org.koin.core.logger.Logger() { override fun display(level: Level, msg: MESSAGE) { @@ -175,9 +179,9 @@ public class Temerity internal constructor( } }) modules( - platformModule(), libModule, - ) + platformModule() + ) } override val koin: Koin = koinApp.koin } @@ -196,6 +200,7 @@ public class Temerity internal constructor( private var platformApi: PlatformApi private var cachedUserRoleList: ConcurrentMutableMap<Int, String> + private var libraryCoroutineDispatcher: CoroutineDispatcher private var libraryCoroutineScope: CoroutineScope init { @@ -207,12 +212,16 @@ public class Temerity internal constructor( "Service url must be provided. Set it using TemClientConfig.serviceUrl(url)" } - libraryCoroutineScope = get<CoroutineScope> { + libraryCoroutineDispatcher = get<CoroutineDispatcher> { + val dispatcherName = "Temerity Library Dispatcher" when (val optThreadCount = config.threadCount) { - null -> parametersOf(DispatcherFactory.calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT)) - else -> parametersOf(DispatcherFactory.setThreadCount(optThreadCount)) + null -> parametersOf(DispatcherFactory.calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT), dispatcherName) + else -> parametersOf(DispatcherFactory.setThreadCount(optThreadCount), dispatcherName) } } + libraryCoroutineScope = get<CoroutineScope> { + parametersOf(libraryCoroutineDispatcher) + } platformApi = get<PlatformApi> { parametersOf(config) diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt index 8574a6a..cbc6a9d 100644 --- a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt @@ -17,9 +17,19 @@ */ package edu.ucsc.its.temerity +import edu.ucsc.its.temerity.core.DispatcherFactory.createDispatcher import io.ktor.client.engine.java.Java +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import org.koin.dsl.module internal actual fun platformModule() = module { - factory { Java.create() } + single { (threadCount: Int, dispatcherName: String) -> + createDispatcher(Dispatchers.IO, threadCount, dispatcherName) + } + factory { (libraryCoroutineDispatcher: CoroutineDispatcher) -> + Java.create { + dispatcher = libraryCoroutineDispatcher + } + } } -- GitLab