Merge pull request #457 from InsanusMokrassar/0.20.52

0.20.52
This commit is contained in:
InsanusMokrassar 2024-06-14 23:57:26 +06:00 committed by GitHub
commit 30440b4ed1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 120 additions and 48 deletions

View File

@ -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`:

View File

@ -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 <T> CoroutineScope.weakAsync(
/**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to create [async] [Deferred]
*/
fun <T> CoroutineScope.asyncWeak(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
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 <T> CoroutineScope.weakAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> = asyncWeak(context, start, block)

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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