![JAR search and dependency download from the Maven repository](/logo.png)
.kovenant.kovenant-core.3.3.0.source-code.promises-jvm.kt Maven / Gradle / Ivy
/*
* Copyright (c) 2015 Mark Platvoet
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* THE SOFTWARE.
*/
package nl.komponents.kovenant
import nl.komponents.kovenant.unsafe.UnsafeAtomicReferenceFieldUpdater
import nl.komponents.kovenant.unsafe.hasUnsafe
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
internal fun concretePromise(context: Context, callable: () -> V): Promise
= TaskPromise(context, callable)
internal fun concretePromise(context: Context, promise: Promise, callable: (V) -> R): Promise
= ThenPromise(context, promise, callable)
internal fun concreteSuccessfulPromise(context: Context, value: V): Promise = SuccessfulPromise(context, value)
internal fun concreteFailedPromise(context: Context, value: E): Promise = FailedPromise(context, value)
internal fun concreteDeferred(context: Context): Deferred = DeferredPromise(context)
internal fun concreteDeferred(context: Context, onCancelled: (E) -> Unit): Deferred = CancelableDeferredPromise(context, onCancelled)
private class SuccessfulPromise(context: Context, value: V) : AbstractPromise(context) {
init {
trySetSuccessResult(value)
}
// Could override the `fail` methods since there is nothing to add
// but any change to those methods might be missed.
// the callbacks essentially get ignored anyway
}
private class FailedPromise(context: Context, value: E) : AbstractPromise(context) {
init {
trySetFailResult(value)
}
// Could override the `success` methods since there is nothing to add
// but any change to those methods might be missed.
// the callbacks essentially get ignored anyway
}
private class ThenPromise(context: Context,
promise: Promise,
callable: (V) -> R) :
SelfResolvingPromise(context),
CancelablePromise {
//need to hold the task to be able to cancel
private @Volatile var task: (() -> Unit)? = null
init {
if (promise.isDone()) {
if (promise.isSuccess()) {
schedule(context, promise.get(), callable)
} else {
reject(promise.getError())
}
} else {
triggered(callable, context, promise)
}
}
private fun triggered(callable: (V) -> R, context: Context, promise: Promise) {
promise.success(DirectDispatcherContext) {
schedule(context, it, callable)
}
promise.fail(DirectDispatcherContext) {
reject(it)
}
}
private fun schedule(context: Context, value: V, callable: (V) -> R) {
val wrapper = {
try {
val result = callable(value)
resolve(result)
} catch(e: Exception) {
reject(e)
} finally {
//avoid leaking memory after a reject/resolve
task = null
}
}
task = wrapper
context.workerContext.offer(wrapper)
}
override fun cancel(error: Exception): Boolean {
val wrapper = task
if (wrapper != null) {
task = null //avoid memory leaking
context.workerContext.dispatcher.tryCancel(wrapper)
if (trySetFailResult(error)) {
fireFail(error)
return true
}
}
return false
}
}
private class TaskPromise(context: Context, callable: () -> V) :
SelfResolvingPromise(context),
CancelablePromise {
private @Volatile var task: (() -> Unit)?
init {
val wrapper = {
try {
val result = callable()
resolve(result)
} catch(e: Exception) {
reject(e)
} finally {
//avoid leaking memory after a reject/resolve
task = null
}
}
task = wrapper
context.workerContext.offer(wrapper)
}
override fun cancel(error: Exception): Boolean {
val wrapper = task
if (wrapper != null) {
task = null //avoid memory leaking
context.workerContext.dispatcher.tryCancel(wrapper)
if (trySetFailResult(error)) {
fireFail(error)
return true
}
}
return false
}
}
private abstract class SelfResolvingPromise(context: Context) : AbstractPromise(context) {
protected fun resolve(value: V) {
if (trySetSuccessResult(value)) {
fireSuccess(value)
}
//no need to report multiple completion here.
//manage this ourselves, can't happen
}
protected fun reject(error: E) {
if (trySetFailResult(error)) {
fireFail(error)
}
//no need to report multiple completion here.
//manage this ourselves, can't happen
}
}
private class DeferredPromise(context: Context) : AbstractPromise(context), Deferred {
override fun resolve(value: V) {
if (trySetSuccessResult(value)) {
fireSuccess(value)
} else {
multipleCompletion(value)
}
}
override fun reject(error: E) {
if (trySetFailResult(error)) {
fireFail(error)
} else {
multipleCompletion(error)
}
}
//Only call this method if we know resolving is eminent.
private fun multipleCompletion(newValue: Any?) {
while (!isDoneInternal()) {
Thread.`yield`()
}
context.multipleCompletion(rawValue(), newValue)
}
override val promise: Promise = object : Promise by this {}
}
private class CancelableDeferredPromise(context: Context, onCancelled: (E) -> Unit) :
AbstractPromise(context), Deferred,
CancelablePromise {
companion object {
//prefer ints over enums for class count
//matters on Android
private val unresolved = 0
private val cancelled = 1
private val failed = 2
private val success = 3
}
//allow callback to be cleaned. Promise can live a long time so avoid
//callback holding to resources not important anymore
private @Volatile var cb: ((E) -> Unit)? = onCancelled
private @Volatile var state = unresolved
override fun resolve(value: V) {
while (state == unresolved) {
if (trySetSuccessResult(value)) {
state = success
fireSuccess(value)
cb = null
return
}
}
//Only report multiple completion if this has not been cancelled
//since in race situations this can simply happen.
if (state != cancelled) {
multipleCompletion(value)
}
}
override fun reject(error: E) {
while (state == unresolved) {
if (trySetFailResult(error)) {
state = failed
fireFail(error)
cb = null
return
}
}
//Only report multiple completion if this has not been cancelled
//since in race situations this can simply happen.
if (state != cancelled) {
multipleCompletion(error)
}
}
override fun cancel(error: E): Boolean {
while (state == unresolved) {
if (trySetFailResult(error)) {
state = cancelled
//Try to actually cancel before firing the callbacks.
//this to avoid situations where someone expects an happens-before
val exc = tryCancelCb(error)
fireFail(error)
cb = null
return when (exc) {
null -> true
else -> throw KovenantException("Promise cancelled but cancel might not have succeeded due to exception in callback", exc)
}
}
}
return false
}
private fun tryCancelCb(error: E): Exception? {
try {
cb?.invoke(error)
} catch (e: Exception) {
return e
}
return null
}
//Only call this method if we know resolving is eminent.
private fun multipleCompletion(newValue: Any?) {
while (!isDoneInternal()) {
Thread.`yield`()
}
context.multipleCompletion(rawValue(), newValue)
}
override val promise: Promise = object : CancelablePromise by this {}
}
private abstract class AbstractPromise(override val context: Context) : Promise {
companion object {
private val stateUpdater: AtomicReferenceFieldUpdater, State>
private val waitingThreadsUpdater: AtomicReferenceFieldUpdater, AtomicInteger>
private val headUpdater: AtomicReferenceFieldUpdater, CallbackContextNode<*, *>>
init {
if (hasUnsafe()) {
stateUpdater = UnsafeAtomicReferenceFieldUpdater(AbstractPromise::class, "state")
waitingThreadsUpdater = UnsafeAtomicReferenceFieldUpdater(AbstractPromise::class, "_waitingThreads")
headUpdater = UnsafeAtomicReferenceFieldUpdater(AbstractPromise::class, "_head")
} else {
stateUpdater = AtomicReferenceFieldUpdater.newUpdater(AbstractPromise::class.java, State::class.java, "state")
waitingThreadsUpdater = AtomicReferenceFieldUpdater.newUpdater(AbstractPromise::class.java, AtomicInteger::class.java, "_waitingThreads")
headUpdater = AtomicReferenceFieldUpdater.newUpdater(AbstractPromise::class.java, CallbackContextNode::class.java, "_head")
}
}
}
private @Volatile var state = State.PENDING
private @Volatile var _waitingThreads: AtomicInteger? = null
private val waitingThreads: AtomicInteger get() {
while (true) {
val m = _waitingThreads
if (m != null) {
return m
}
waitingThreadsUpdater.compareAndSet(this, null, AtomicInteger(0))
}
}
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private val mutex: Object get() = waitingThreads as Object
private @Volatile var _head: CallbackContextNode? = null
private @Volatile var result: Any? = null
override fun success(context: DispatcherContext, callback: (value: V) -> Unit): Promise {
if (isFailureInternal()) return this
//Bypass the queue if this promise is resolved and the queue is empty
//no need to create excess nodes
if (isSuccessInternal() && isEmptyCallbacks()) {
context.offer { callback(getAsValueResult()) }
return this
}
addSuccessCb(context, callback)
//possibly resolved already
if (isSuccessInternal()) fireSuccess(getAsValueResult())
return this
}
override fun fail(context: DispatcherContext, callback: (error: E) -> Unit): Promise {
if (isSuccessInternal()) return this
//Bypass the queue if this promise is resolved and the queue is empty
//no need to create excess nodes
if (isFailureInternal() && isEmptyCallbacks()) {
context.offer { callback(getAsFailResult()) }
return this
}
addFailCb(context, callback)
//possibly rejected already
if (isFailureInternal()) fireFail(getAsFailResult())
return this
}
override fun always(context: DispatcherContext, callback: () -> Unit): Promise {
//Bypass the queue if this promise is resolved and the queue is empty
//no need to create excess nodes
if ((isSuccessInternal() || isFailureInternal()) && isEmptyCallbacks()) {
context.offer { callback() }
return this
}
addAlwaysCb(context, callback)
//possibly completed already
when {
isSuccessInternal() -> fireSuccess(getAsValueResult())
isFailureInternal() -> fireFail((getAsFailResult()))
}
return this
}
override fun get(): V {
if (!isDoneInternal()) {
waitingThreads.incrementAndGet()
try {
synchronized(mutex) {
while (!isDoneInternal()) {
try {
mutex.wait()
} catch(e: InterruptedException) {
throw FailedException(e)
}
}
}
} finally {
waitingThreads.decrementAndGet()
}
}
if (isSuccessInternal()) {
return getAsValueResult()
} else {
throw getAsFailResult().asException()
}
}
override fun getError(): E {
if (!isDoneInternal()) {
waitingThreads.incrementAndGet()
try {
synchronized(mutex) {
while (!isDoneInternal()) {
try {
mutex.wait()
} catch(e: InterruptedException) {
throw FailedException(e)
}
}
}
} finally {
waitingThreads.decrementAndGet()
}
}
if (isFailureInternal()) {
return getAsFailResult()
} else {
throw FailedException(getAsValueResult())
}
}
fun fireSuccess(value: V) = popAll {
node ->
node.runSuccess(value)
}
fun fireFail(value: E) = popAll {
node ->
node.runFail(value)
}
private enum class State {
PENDING,
MUTATING,
SUCCESS,
FAIL
}
private enum class NodeState {
CHAINED,
POPPING,
APPENDING
}
fun trySetSuccessResult(result: V): Boolean {
if (state != State.PENDING) return false
if (stateUpdater.compareAndSet(this, State.PENDING, State.MUTATING)) {
this.result = result
state = State.SUCCESS
notifyBlockedThreads()
return true
}
return false
}
fun trySetFailResult(result: E): Boolean {
if (state != State.PENDING) return false
if (stateUpdater.compareAndSet(this, State.PENDING, State.MUTATING)) {
this.result = result
state = State.FAIL
notifyBlockedThreads()
return true
}
return false
}
private fun notifyBlockedThreads() {
val i = _waitingThreads
if (i != null && i.get() > 0) {
synchronized(mutex) {
mutex.notifyAll()
}
}
}
protected fun isDoneInternal(): Boolean {
val currentState = state
return currentState == State.SUCCESS || currentState == State.FAIL
}
protected fun isSuccessInternal(): Boolean = state == State.SUCCESS
protected fun isFailureInternal(): Boolean = state == State.FAIL
override fun isDone(): Boolean = isDoneInternal()
override fun isFailure(): Boolean = isFailureInternal()
override fun isSuccess(): Boolean = isSuccessInternal()
//For internal use only! Method doesn't check anything, just casts.
@Suppress("UNCHECKED_CAST")
private fun getAsValueResult(): V = result as V
//For internal use only! Method doesn't check anything, just casts.
@Suppress("UNCHECKED_CAST")
private fun getAsFailResult(): E = result as E
protected fun rawValue(): Any = result as Any
private fun addSuccessCb(context: DispatcherContext, cb: (V) -> Unit) = addValueNode(SuccessCallbackContextNode(context, cb))
private fun addFailCb(context: DispatcherContext, cb: (E) -> Unit) = addValueNode(FailCallbackContextNode(context, cb))
private fun addAlwaysCb(context: DispatcherContext, cb: () -> Unit) = addValueNode(AlwaysCallbackContextNode(context, cb))
private fun createHeadNode(): CallbackContextNode = EmptyCallbackContextNode()
private fun addValueNode(node: CallbackContextNode) {
while (true) {
val tail = findTailNode()
if (tail.compareAndSet(NodeState.CHAINED, NodeState.APPENDING)) {
if (tail.next == null) {
tail.next = node
tail.nodeState = NodeState.CHAINED
return
}
tail.nodeState = NodeState.CHAINED
}
}
}
private fun getOrCreateHead(): CallbackContextNode {
while (true) {
val h = _head
if (h != null) {
return h
}
headUpdater.compareAndSet(this, null, createHeadNode())
}
}
private fun findTailNode(): CallbackContextNode {
var tail = getOrCreateHead()
while (true) tail = tail.next ?: break
return tail
}
private fun isEmptyCallbacks(): Boolean {
val headNode = _head
return headNode == null || headNode.next == null
}
private inline fun popAll(fn: (CallbackContext) -> Unit) {
val localHead = _head
if (localHead != null) {
do {
val popper = localHead.next
if (popper != null) {
if (localHead.compareAndSet(NodeState.CHAINED, NodeState.POPPING)) {
if (popper.compareAndSet(NodeState.CHAINED, NodeState.POPPING)) {
localHead.next = popper.next
localHead.nodeState = NodeState.CHAINED
popper.next = null
fn(popper)
}
localHead.nodeState = NodeState.CHAINED
}
}
} while (popper != null)
}
}
private interface CallbackContext {
fun runSuccess(value: V)
fun runFail(value: E)
}
private abstract class CallbackContextNode : CallbackContext {
companion object {
private val nodeStateUpdater: AtomicReferenceFieldUpdater, NodeState>
init {
nodeStateUpdater = if (hasUnsafe()) {
UnsafeAtomicReferenceFieldUpdater(CallbackContextNode::class, "nodeState")
} else {
AtomicReferenceFieldUpdater.newUpdater(CallbackContextNode::class.java, NodeState::class.java, "nodeState")
}
}
}
fun compareAndSet(expected: NodeState, update: NodeState): Boolean {
return nodeStateUpdater.compareAndSet(this, expected, update)
}
@Volatile var next: CallbackContextNode? = null
@Volatile var nodeState = NodeState.CHAINED
}
private class EmptyCallbackContextNode : CallbackContextNode() {
override fun runSuccess(value: V) {
//ignore
}
override fun runFail(value: E) {
//ignore
}
}
private class AlwaysCallbackContextNode(private val context: DispatcherContext,
private val fn: () -> Unit) : CallbackContextNode() {
override fun runSuccess(value: V) = context.offer { fn() }
override fun runFail(value: E) = context.offer { fn() }
}
private class SuccessCallbackContextNode(private val context: DispatcherContext,
private val fn: (V) -> Unit) : CallbackContextNode() {
override fun runSuccess(value: V) = context.offer { fn(value) }
override fun runFail(value: E) {
//ignore
}
}
private class FailCallbackContextNode(private val context: DispatcherContext,
private val fn: (E) -> Unit) : CallbackContextNode() {
override fun runSuccess(value: V) {
//ignore
}
override fun runFail(value: E) = context.offer { fn(value) }
}
}
private fun T.asException(): Exception {
return when (this) {
is Exception -> this
else -> FailedException(this)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy