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

dev.tauri.choam.internal.mcas.Descriptor.scala Maven / Gradle / Ivy

There is a newer version: 0.4.10-3-3a25cbb
Show newest version
/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright 2016-2024 Daniel Urban and contributors listed in NOTICE.txt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dev.tauri.choam
package internal
package mcas

import scala.util.hashing.MurmurHash3

final class Descriptor private (
  protected final override val map: LogMap2[Any],
  final override val validTs: Long,
  private val validTsBoxed: java.lang.Long,
  final override val versionIncr: Long,
  protected final override val versionCas: LogEntry[java.lang.Long], // can be null
) extends DescriptorPlatform {

  require((versionCas eq null) || (versionIncr > 0L))
  require((validTsBoxed eq null) || (validTsBoxed.longValue == validTs))

  final override type D = Descriptor

  final override val readOnly: Boolean =
    this.map.definitelyReadOnly && (!this.hasVersionCas)

  final override def toImmutable: Descriptor =
    this

  protected final override def hamt: AbstractHamt[_, _, _, _, _, _] =
    this.map

  private[mcas] final override def hasVersionCas: Boolean = {
    this.versionCas ne null
  }

  private[mcas] final override def addVersionCas(commitTsRef: MemoryLocation[Long]): Descriptor = {
    require(this.versionCas eq null)
    require(!this.readOnly)
    require(this.versionIncr > 0L)
    require(this.validTsBoxed ne null)
    val hwd = LogEntry[java.lang.Long](
      commitTsRef.asInstanceOf[MemoryLocation[java.lang.Long]],
      ov = this.validTsBoxed, // no re-boxing here
      nv = java.lang.Long.valueOf(this.newVersion),
      version = Version.Start, // the version's version is unused/arbitrary
    )
    new Descriptor(
      map = this.map,
      validTs = this.validTs,
      validTsBoxed = this.validTsBoxed,
      versionIncr = this.versionIncr,
      versionCas = hwd,
    )
  }

  private[choam] final override def getOrElseNull[A](ref: MemoryLocation[A]): LogEntry[A] = {
    this.map.asInstanceOf[LogMap2[A]].getOrElseNull(ref.id)
  }

  private[choam] final override def add[A](desc: LogEntry[A]): Descriptor = {
    // Note, that it is important, that we don't allow
    // adding an already included ref; the Exchanger
    // depends on this behavior:
    val newMap = this.map.inserted(desc.cast[Any])
    this.withLogMap(newMap)
  }

  private[choam] final override def overwrite[A](desc: LogEntry[A]): Descriptor = {
    require(desc.version <= this.validTs)
    val newMap = this.map.updated(desc.cast[Any])
    this.withLogMap(newMap)
  }

  private[choam] final def addOrOverwrite[A](desc: LogEntry[A]): Descriptor = {
    require(desc.version <= this.validTs)
    val newMap = this.map.upserted(desc.cast[Any])
    this.withLogMap(newMap)
  }

  private[choam] final override def computeIfAbsent[A, T](
    ref: MemoryLocation[A],
    tok: T,
    visitor: Hamt.EntryVisitor[MemoryLocation[A], LogEntry[A], T],
  ): Descriptor = {
    val newMap = this.map.computeIfAbsent(ref.cast[Any], tok, visitor.asInstanceOf[Hamt.EntryVisitor[MemoryLocation[Any], LogEntry[Any], T]])
    if (newMap eq this.map) {
      this
    } else {
      this.withLogMap(newMap)
    }
  }

  private[choam] final override def computeOrModify[A, T](
    ref: MemoryLocation[A],
    tok: T,
    visitor: Hamt.EntryVisitor[MemoryLocation[A], LogEntry[A], T],
  ): Descriptor = {
    val newMap = this.map.computeOrModify(ref.cast[Any], tok, visitor.asInstanceOf[Hamt.EntryVisitor[MemoryLocation[Any], LogEntry[Any], T]])
    if (newMap eq this.map) {
      this
    } else {
      this.withLogMap(newMap)
    }
  }

  /**
   * Tries to revalidate `this` based on the current
   * versions of the refs it contains.
   *
   * @return true, iff `this` is still valid.
   */
  private[mcas] final override def revalidate(ctx: Mcas.ThreadContext): Boolean = {
    this.map.revalidate(ctx)
  }

  private[mcas] final override def validateAndTryExtend(
    commitTsRef: MemoryLocation[Long],
    ctx: Mcas.ThreadContext,
    additionalHwd: LogEntry[_], // can be null
  ): Descriptor = {
    require(this.versionCas eq null)
    // NB: we must read the commitTs *before* the `ctx.validate...`
    val newValidTsBoxed: java.lang.Long =
      (ctx.readDirect(commitTsRef) : Any).asInstanceOf[java.lang.Long]
    this.validateAndTryExtendInternal(newValidTsBoxed.longValue, newValidTsBoxed, ctx, additionalHwd)
  }

  private[mcas] final def validateAndTryExtendVer(
    currentTs: Long,
    ctx: Mcas.ThreadContext,
    additionalHwd: LogEntry[_], // can be null
  ): Descriptor = {
    // We're passing `newValidTsBoxed = null`. This is ugly,
    // but nothing will actually need it (we're doing EMCAS
    // if we're here), and this way we avoid allocating
    // an extra `java.lang.Long`.
    this.validateAndTryExtendInternal(
      newValidTs = currentTs,
      newValidTsBoxed = null, // see above
      ctx = ctx,
      additionalHwd = additionalHwd,
    )
  }

  private[this] final def validateAndTryExtendInternal(
    newValidTs: Long,
    newValidTsBoxed: java.lang.Long, // can be null
    ctx: Mcas.ThreadContext,
    additionalHwd: LogEntry[_], // can be null
  ): Descriptor = {
    if (newValidTs > this.validTs) {
      if (
        ctx.validate(this) &&
        ((additionalHwd eq null) || ctx.validateHwd(additionalHwd))
      ) {
        assert((additionalHwd eq null) || (additionalHwd.version <= newValidTs))
        new Descriptor(
          map = this.map,
          validTs = newValidTs,
          validTsBoxed = newValidTsBoxed,
          versionIncr = this.versionIncr,
          versionCas = this.versionCas,
        )
      } else {
        null
      }
    } else {
      // no need to validate:
      this
    }
  }

  private[mcas] final def withNoNewVersion: Descriptor = {
    require(this.versionCas eq null)
    new Descriptor(
      map = this.map,
      validTs = this.validTs,
      validTsBoxed = this.validTsBoxed,
      versionIncr = 0L,
      versionCas = null,
    )
  }

  private final def withLogMap(newMap: LogMap2[Any]): Descriptor = {
    if (newMap eq this.map) {
      this
    } else {
      new Descriptor(
        map = newMap,
        validTs = this.validTs,
        validTsBoxed = this.validTsBoxed,
        versionIncr = this.versionIncr,
        versionCas = this.versionCas,
      )
    }
  }

  final override def toString: String = {
    val m = this.map.toString(pre = "[", post = "]")
    val vi = if (versionIncr == Descriptor.DefaultVersionIncr) {
      ""
    } else {
      s", versionIncr = ${versionIncr}"
    }
    val vc = if (versionCas eq null) {
      ""
    } else {
      s", versionCas = ${versionCas}"
    }
    s"mcas.Descriptor(${m}, validTs = ${validTs}, readOnly = ${readOnly}${vi}${vc})"
  }

  final override def equals(that: Any): Boolean = {
    that match {
      case that: Descriptor =>
        (this eq that) || (
          (this.versionCas == that.versionCas) &&
          (this.validTs == that.validTs) &&
          (this.validTsBoxed eq that.validTsBoxed) &&
          (this.versionIncr == that.versionIncr) &&
          (this.map == that.map)
        )
      case _ =>
        false
    }
  }

  final override def hashCode: Int = {
    var h = MurmurHash3.mix(0xefebde66, this.validTs.##)
    h = MurmurHash3.mix(h, this.versionIncr.##)
    h = MurmurHash3.mix(h, this.versionCas.##)
    h = MurmurHash3.mix(h, this.map.##)
    MurmurHash3.finalizeHash(h, this.map.size)
  }
}

object Descriptor {

  private final val DefaultVersionIncr =
    Version.Incr

  private[mcas] final def empty(commitTsRef: MemoryLocation[Long], ctx: Mcas.ThreadContext): Descriptor = {
    val validTsBoxed: java.lang.Long =
      (ctx.readDirect(commitTsRef) : Any).asInstanceOf[java.lang.Long]
    new Descriptor(
      LogMap2.empty,
      validTs = validTsBoxed.longValue,
      validTsBoxed = validTsBoxed,
      versionIncr = DefaultVersionIncr,
      versionCas = null,
    )
  }

  private[mcas] final def emptyFromVer(currentTs: Long): Descriptor = {
    // We're passing `validTsBoxed = null`. This is ugly,
    // but nothing will actually need it (we're doing EMCAS
    // if we're here), and this way we avoid allocating
    // an extra `java.lang.Long`.
    new Descriptor(
      LogMap2.empty,
      validTs = currentTs,
      validTsBoxed = null, // see above
      versionIncr = DefaultVersionIncr,
      versionCas = null,
    )
  }

  private[mcas] final def fromLogMapAndVer(
    map: LogMap2[Any],
    validTs: Long,
    versionIncr: Long,
  ): Descriptor = {
    new Descriptor(
      map = map,
      validTs = validTs,
      validTsBoxed = null, // see above
      versionIncr = versionIncr,
      versionCas = null,
    )
  }

  private[mcas] final def merge(
    a: Descriptor,
    b: Descriptor,
    ctx: Mcas.ThreadContext,
  ): Descriptor = {
    require(a.versionCas eq null)
    require(b.versionCas eq null)
    require(a.versionIncr == b.versionIncr)
    // TODO: It is unclear, how should an exchange work when
    // TODO: both sides already touched the same refs;
    // TODO: for now, we only allow disjoint logs.
    // TODO: (This seems to make exchanges fundamentally
    // TODO: non-composable. Unless a solution is found
    // TODO: to this problem, an elimination stack (e.g.)
    // TODO: cannot be used in bigger `Rxn`s, because
    // TODO: by the time the elimination happens, the
    // TODO: two bigger `Rxn`s might've already touched
    // TODO: the same ref.)
    val mergedMap = a.map.insertedAllFrom(b.map) // throws in case of conflict

    // we temporarily choose the older `validTs`,
    // but will extend if they're not equal:
    var merged: Descriptor = null
    val needToExtend = if (a.validTs < b.validTs) {
      merged = a.withLogMap(mergedMap)
      true
    } else if (a.validTs > b.validTs) {
      merged = b.withLogMap(mergedMap)
      true
    } else {
      // they're equal, no need to extend:
      merged = a.withLogMap(mergedMap)
      false
    }
    if (needToExtend) {
      merged = ctx.validateAndTryExtend(merged, hwd = null) match {
        case null =>
          // couldn't extend:
          null
        case extended =>
          // we know it's immutable here
          // (so `toImmutable` is a NOP),
          // we just need to convince scalac
          // that it's type is `Descriptor`:
          val r = extended.toImmutable
          assert(r eq extended)
          r
      }
    }
    merged
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy