com.fireflysource.common.coroutine.CoroutineLocal.kt Maven / Gradle / Ivy
package com.fireflysource.common.coroutine
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* Retain a value in the coroutine scope.
*
* @author Pengtao Qiu
*/
class CoroutineLocal {
private val threadLocal = ThreadLocal()
/**
* Convert a value to the coroutine context element.
*
* @param value A value runs through in the coroutine scope.
* @return The coroutine context element.
*/
fun asElement(value: T) = threadLocal.asContextElement(value)
/**
* Get the value in the coroutine scope.
*
* @return The value in the coroutine scope.
*/
fun get(): T? = threadLocal.get()
/**
* Set the value in the coroutine scope.
*
* @param value The value in the coroutine scope.
*/
fun set(value: T?) = threadLocal.set(value)
}
/**
* Retain the attributes in the coroutine scope.
*
* @author Pengtao Qiu
*/
object CoroutineLocalContext {
private val ctx: CoroutineLocal> by lazy { CoroutineLocal>() }
/**
* Convert the attributes to the coroutine context element.
*
* @param attributes The attributes run through in the coroutine scope.
* @return The coroutine context element.
*/
fun asElement(attributes: MutableMap): ThreadContextElement> =
ctx.asElement(HashMap(attributes))
/**
* Inherit parent coroutine context element, and merge it into current attributes.
*
* @param attributes The attributes run through in the coroutine scope.
* @return The coroutine context element.
*/
fun inheritParentElement(attributes: MutableMap? = null): ThreadContextElement> {
val newAttributes = HashMap()
val parentAttributes = getAttributes()
if (parentAttributes != null) {
newAttributes.putAll(parentAttributes)
}
if (attributes != null) {
newAttributes.putAll(attributes)
}
return ctx.asElement(newAttributes)
}
/**
* Get the current attributes.
*
* @return The current attributes.
*/
fun getAttributes(): MutableMap? = ctx.get()
/**
* Get an attribute in the current coroutine scope.
*
* @param name The name of attribute.
* @return An attribute in the current coroutine scope.
*/
inline fun getAttr(name: String): T? {
return getAttributes()?.get(name) as T?
}
/**
* Get an attribute in the current coroutine scope, if the value is null return the default value.
*
* @param name The name of attribute.
* @param func Get the default value lazily.
* @return An attribute in the current coroutine scope, or the default value.
*/
inline fun getAttrOrDefault(name: String, crossinline func: (String) -> T): T {
return getAttributes()?.get(name) as T? ?: func(name)
}
/**
* Set an attribute in the current coroutine scope.
*
* @param name The attribute's name.
* @param value The attribute's value.
* @return The old value in the current coroutine scope.
*/
inline fun setAttr(name: String, value: T): T? {
return getAttributes()?.put(name, value) as T?
}
/**
* If the specified attribute name does not already associate with a value (or is mapped
* to null), attempts to compute its value using the given mapping
* function and enters it into this map unless null.
*
* @param name The attribute's name.
* @param func The mapping function.
* @return The value in the current coroutine scope.
*/
inline fun computeIfAbsent(name: String, crossinline func: (String) -> T): T? {
return getAttributes()?.computeIfAbsent(name) { func(it) } as T?
}
}
fun CoroutineScope.inheritableAsync(
context: CoroutineContext = EmptyCoroutineContext,
attributes: MutableMap? = null,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred {
return this.async(context + CoroutineLocalContext.inheritParentElement(attributes), start) { block(this) }
}
fun CoroutineScope.inheritableLaunch(
context: CoroutineContext = EmptyCoroutineContext,
attributes: MutableMap? = null,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
return this.launch(context + CoroutineLocalContext.inheritParentElement(attributes), start) { block(this) }
}
/**
* Starts an asynchronous task waiting the result and inherits parent coroutine local attributes.
*
* @param context Additional to [CoroutineScope.coroutineContext] context of the coroutine.
* @param attributes The attributes merge into the parent coroutine context element.
* @param block The coroutine code block.
* @return The result.
*/
suspend fun withContextInheritable(
context: CoroutineContext = EmptyCoroutineContext,
attributes: MutableMap? = null,
block: suspend CoroutineScope.() -> T
): T {
return withContext(context + CoroutineLocalContext.inheritParentElement(attributes)) { block(this) }
}
val computationScope: CoroutineScope = CoroutineScope(CoroutineDispatchers.computation + CoroutineName("FireflyCommonComputation"))
val ioBlockingScope: CoroutineScope = CoroutineScope(CoroutineDispatchers.ioBlocking + CoroutineName("FireflyCommonIOBlocking"))
val singleThreadScope: CoroutineScope = CoroutineScope(CoroutineDispatchers.ioBlocking + CoroutineName("FireflyCommonSingleThread"))
inline fun compute(crossinline block: suspend CoroutineScope.() -> Unit): Job =
computationScope.launch(CoroutineDispatchers.computation) { block(this) }
inline fun computeAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred =
computationScope.async(CoroutineDispatchers.computation) { block(this) }
inline fun blocking(crossinline block: suspend CoroutineScope.() -> Unit): Job =
ioBlockingScope.launch(CoroutineDispatchers.ioBlocking) { block(this) }
inline fun blockingAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred =
ioBlockingScope.async(CoroutineDispatchers.ioBlocking) { block(this) }
inline fun event(crossinline block: suspend CoroutineScope.() -> Unit): Job =
singleThreadScope.launch(CoroutineDispatchers.singleThread) { block(this) }
inline fun eventAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred =
singleThreadScope.async(CoroutineDispatchers.singleThread) { block(this) }
inline fun CoroutineScope.blocking(crossinline block: suspend CoroutineScope.() -> Unit): Job =
this.launch(CoroutineDispatchers.ioBlocking) { block(this) }
inline fun CoroutineScope.blockingAsync(crossinline block: suspend CoroutineScope.() -> T): Deferred =
this.async(CoroutineDispatchers.ioBlocking) { block(this) }