diff --git a/CHANGELOG.md b/CHANGELOG.md index bb99a5d0e5a..3db6aa60047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.20.52 + +* `Coroutines`: + * Small rework of weak jobs: add `WeakScope` factory, rename old weal extensions and add kdocs + ## 0.20.51 * `Versions`: diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt index c020a359ecb..a00840d8f37 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt @@ -4,28 +4,71 @@ import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope -> - coroutineContext.job.invokeOnCompletion { newScope.cancel() } +/** + * Created [CoroutineScope] which will [launch] listening of [context] job completing and drop itself. Current weak + * scope **will not** be attached to [context] directly. So, this [CoroutineScope] will not prevent parent one from + * cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status + * of parent [Job] + */ +fun WeakScope( + context: CoroutineContext +) = CoroutineScope(context.minusKey(Job) + Job()).also { newScope -> + newScope.launch { + context.job.join() + newScope.cancel() + } } -fun CoroutineScope.weakLaunch( +/** + * Created [CoroutineScope] which will [launch] listening of [scope] [CoroutineContext] job completing and drop itself. Current weak + * scope **will not** be attached to [scope] [CoroutineContext] directly. So, this [CoroutineScope] will not prevent parent one from + * cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status + * of parent [Job] + */ +fun WeakScope( + scope: CoroutineScope +) = WeakScope(scope.coroutineContext) + +/** + * [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block]) + * will be used to [launch] [Job] + */ +fun CoroutineScope.launchWeak( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { - val scope = createWeakSubScope() + val scope = WeakScope(this) val job = scope.launch(context, start, block) job.invokeOnCompletion { scope.cancel() } return job } -fun CoroutineScope.weakAsync( +/** + * [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block]) + * will be used to create [async] [Deferred] + */ +fun CoroutineScope.asyncWeak( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred { - val scope = createWeakSubScope() + val scope = WeakScope(this) val deferred = scope.async(context, start, block) deferred.invokeOnCompletion { scope.cancel() } return deferred } + +@Deprecated("Renamed", ReplaceWith("launchWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.launchWeak")) +fun CoroutineScope.weakLaunch( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +): Job = launchWeak(context, start, block) + +@Deprecated("Renamed", ReplaceWith("asyncWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.asyncWeak")) +fun CoroutineScope.weakAsync( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): Deferred = asyncWeak(context, start, block) diff --git a/coroutines/src/commonTest/kotlin/WeakJobTests.kt b/coroutines/src/commonTest/kotlin/WeakJobTests.kt new file mode 100644 index 00000000000..0a973465896 --- /dev/null +++ b/coroutines/src/commonTest/kotlin/WeakJobTests.kt @@ -0,0 +1,64 @@ +import dev.inmo.micro_utils.coroutines.asyncWeak +import dev.inmo.micro_utils.coroutines.launchWeak +import kotlinx.coroutines.* +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertTrue + +class WeakJobTests { + @Test + fun testWeakJob() = runTest { + var commonJobDone = false + var weakJobStarted = false + try { + coroutineScope { + launch { + delay(1000) + commonJobDone = true + } + asyncWeak { + weakJobStarted = true + delay(100500L) + error("This must never happen") + } + }.await() + } catch (error: Throwable) { + assertTrue(error is CancellationException) + assertTrue(commonJobDone) + assertTrue(weakJobStarted) + return@runTest + } + error("Cancellation exception has not been thrown") + } + @Test + fun testThatWeakJobsWorksCorrectly() = runTest { + val scope = CoroutineScope(Dispatchers.Default) + lateinit var weakLaunchJob: Job + lateinit var weakAsyncJob: Job + val completeDeferred = Job() + coroutineScope { + weakLaunchJob = launchWeak { + while (isActive) { + delay(100L) + } + } + weakAsyncJob = asyncWeak { + while (isActive) { + delay(100L) + } + } + + coroutineContext.job.invokeOnCompletion { + scope.launch { + delay(1000L) + completeDeferred.complete() + } + } + launch { delay(1000L); cancel() } + } + completeDeferred.join() + + assertTrue(!weakLaunchJob.isActive) + assertTrue(!weakAsyncJob.isActive) + } +} \ No newline at end of file diff --git a/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt deleted file mode 100644 index 311b3b05b3b..00000000000 --- a/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/WeakJob.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.inmo.micro_utils.coroutines - -import kotlinx.coroutines.* -import org.junit.Test - -class WeakJob { - @Test - fun `test that weak jobs works correctly`() { - val scope = CoroutineScope(Dispatchers.Default) - lateinit var weakLaunchJob: Job - lateinit var weakAsyncJob: Job - scope.launchSynchronously { - val completeDeferred = Job() - coroutineScope { - weakLaunchJob = weakLaunch { - while (isActive) { - delay(100L) - } - } - weakAsyncJob = weakAsync { - while (isActive) { - delay(100L) - } - } - - coroutineContext.job.invokeOnCompletion { - scope.launch { - delay(1000L) - completeDeferred.complete() - } - } - launch { delay(1000L); cancel() } - } - completeDeferred.join() - } - - assert(!weakLaunchJob.isActive) - assert(!weakAsyncJob.isActive) - } -} diff --git a/gradle.properties b/gradle.properties index 3ba75f5b0ec..c2138181643 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.20.51 -android_code_version=257 +version=0.20.52 +android_code_version=258