All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.arrow.continuations.generic.SuspendingComputation.kt Maven / Gradle / Ivy

package arrow.continuations.generic

import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
import kotlin.coroutines.resumeWithException

internal const val UNDECIDED = 0
internal const val SUSPENDED = 1

@Suppress("UNCHECKED_CAST")
internal open class SuspendMonadContinuation(
  private val parent: Continuation,
  val f: suspend SuspendedScope.() -> R
) : Continuation, SuspendedScope {

  /**
   * State is either
   *  0 - UNDECIDED
   *  1 - SUSPENDED
   *  Any? (3) `resumeWith` always stores it upon UNDECIDED, and `getResult` can atomically get it.
   */
  private val _decision = AtomicRef(UNDECIDED)
  private val token: Token = Token()

  override val context: CoroutineContext = parent.context

  override fun resumeWith(result: Result) {
    _decision.loop { decision ->
      when (decision) {
        UNDECIDED -> {
          val r: R? = result.fold({ it }) { EMPTY_VALUE.unbox(it.shiftedOrNull()) }
          when {
            r == null -> {
              parent.resumeWithException(result.exceptionOrNull()!!)
              return
            }
            _decision.compareAndSet(UNDECIDED, r) -> {
              return
            }
            else -> Unit // loop again
          }
        }
        else -> { // If not `UNDECIDED` then we need to pass result to `parent`
          val res: Result = result.fold(
            { Result.success(it) },
            { t ->
              val x = t.shiftedOrNull()
              if (x === EMPTY_VALUE) Result.failure(t)
              else Result.success(EMPTY_VALUE.unbox(x))
            }
          )
          parent.resumeWith(res)
          return
        }
      }
    }
  }

  @PublishedApi // return the result
  internal fun getResult(): Any? =
    _decision.loop { decision ->
      when (decision) {
        UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return COROUTINE_SUSPENDED
        else -> return decision
      }
    }

  // If ShortCircuit causes CancellationException, we also want to shift back to R
  private tailrec fun Throwable.shortCircuitCause(): ShortCircuit? =
    when (val cause = this.cause) {
      null -> null
      is ShortCircuit -> cause
      else -> cause.shortCircuitCause()
    }

  private fun Throwable.shiftedOrNull(): Any? {
    val shortCircuit = if (this is ShortCircuit) this else shortCircuitCause()
    return if (shortCircuit != null && shortCircuit.token === token) shortCircuit.raiseValue as R
    else EMPTY_VALUE
  }

  public override suspend fun  shift(r: R): A =
    throw ShortCircuit(token, r)

  fun startCoroutineUninterceptedOrReturn(): Any? =
    try {
      f.startCoroutineUninterceptedOrReturn(this, this)?.let {
        if (it == COROUTINE_SUSPENDED) getResult()
        else it
      }
    } catch (e: Throwable) {
      val x = e.shiftedOrNull()
      if (x === EMPTY_VALUE) throw e else x
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy