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

dev.tauri.choam.internal.mcas.emcas.EmcasThreadContext.scala Maven / Gradle / Ivy

The 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
package emcas

import java.lang.ref.WeakReference
import java.util.concurrent.ThreadLocalRandom

/** Ctor must be called on the thread of the new instance! */
private final class EmcasThreadContext(
  final override val impl: Emcas,
) extends EmcasThreadContextBase
  with Mcas.UnsealedThreadContext {

  final override type START = MutDescriptor

  private[this] val thread: Thread =
    Thread.currentThread()

  private[mcas] val tid: Long =
    thread.getId()

  // NB: it is a `val`, not a `def`
  final override val random: ThreadLocalRandom =
    ThreadLocalRandom.current()

  // NB: it is a `val`, not a `def`
  final override val refIdGen: RefIdGen =
    RefIdGen.global.newThreadLocal()

  private[this] var markerUsedCount: Int =
    0

  private[this] final val maxMarkerUsedCount =
    4096

  /**
   * When storing a `EmcasWordDesc` into a ref, `EMCAS` first
   * tries to reuse an existing weakref and marker in the ref
   * (see there). If that is not possible, we need another
   * (full) weakref and mark.
   *
   * We could create a fresh one each time, but that could cause
   * a lot of `WeakReference`s to exist simultaneously in
   * some scenarios (e.g., when creating a lot of refs, which
   * are only used once or twice). A lot of `WeakReference`s
   * seem to force the GC to work much harder, which causes
   * bad performance.
   *
   * So, instead of creating a fresh weakref and mark, we ask
   * the `ThreadContext`, to give us a (possibly) reused one.
   * Here we cache a weakref, and reuse it for a while. We don't
   * want to reuse it forever (even if it's not cleared), since
   * each ref which uses the same marker is "bound" together
   * regarding their descriptor cleanup. So, occasionally we create
   * a fresh one (see `markerUsedCount`). (Using the same marker
   * for a limited number of refs is probably not a big deal.)
   */
  private[this] var markerWeakRef: WeakReference[AnyRef] =
    null

  // This is ugly: `getReusableWeakRef` MUST be called
  // immediately after `getReusableMarker`, and the caller
  // MUST hold a strong ref to the return value of
  // `getReusableMarker`. (Otherwise there would be a race
  // with the GC; if the GC wins, we get an empty WeakRef.)
  // But this way we can avoid allocating a 2-tuple to
  // hold both the marker and weakref.

  private[mcas] final def getReusableMarker(): AnyRef = {
    val mwr = this.markerWeakRef
    if (mwr eq null) {
      // nothing to reuse, create new:
      this.createReusableMarker()
    } else {
      val mark = mwr.get()
      if (mark eq null) {
        // nothing to reuse, create new:
        this.createReusableMarker()
      } else {
        // maybe we can reuse it:
        val newMuc = this.markerUsedCount + 1
        this.markerUsedCount = newMuc
        if (newMuc == maxMarkerUsedCount) {
          // we used it too much, create new:
          this.createReusableMarker()
        } else {
          // OK, reuse it:
          mark
        }
      }
    }
  }

  private[mcas] final def getReusableWeakRef(): WeakReference[AnyRef] = {
    this.markerWeakRef
  }

  private[this] final def createReusableMarker(): AnyRef = {
    val mark = new McasMarker
    this.markerWeakRef = new WeakReference(mark)
    if (this.markerUsedCount > this.getMaxReuseEverP()) {
      // TODO: this is not exactly correct, because
      // TODO: even if we `getReusableWeakRef` from
      // TODO: this thread context, we do not necessarily
      // TODO: use it (the CAS to install it may fail)
      this.setMaxReuseEverP(this.markerUsedCount)
    }
    this.markerUsedCount = 0
    mark // caller MUST hold a strong ref
  }

  private[emcas] final def isCurrentContext(): Boolean = {
    this.thread eq Thread.currentThread()
  }

  final override def tryPerformInternal(desc: AbstractDescriptor, optimism: Long): Long =
    impl.tryPerformInternal(desc, this, optimism)

  final override def readDirect[A](ref: MemoryLocation[A]): A =
    impl.readDirect(ref, this)

  final override def readIntoHwd[A](ref: MemoryLocation[A]): LogEntry[A] = {
    val hwd = impl.readIntoHwd(ref, this)
    assert(hwd.readOnly)
    hwd
  }

  protected[choam] final override def readVersion[A](ref: MemoryLocation[A]): Long =
    impl.readVersion(ref, this)

  final override def start(): MutDescriptor =
    MutDescriptor.newEmptyFromVer(this.impl.getCommitTs())

  final override def startSnap(): Descriptor =
    Descriptor.emptyFromVer(this.impl.getCommitTs())

  protected[mcas] final override def addVersionCas(desc: AbstractDescriptor): AbstractDescriptor.Aux[desc.D] =
    desc // we increment the global commit version differently

  final override def validateAndTryExtend(
    desc: AbstractDescriptor,
    hwd: LogEntry[_],
  ): AbstractDescriptor.Aux[desc.D] = {
    desc.validateAndTryExtendVer(this.impl.getCommitTs(), this, hwd)
  }

  final override def toString: String =
    s"ThreadContext(impl = ${this.impl}, tid = ${this.tid})"

  private[choam] final override def recordCommit(retries: Int, committedRefs: Int, descExtensions: Int): Unit = {
    this.recordCommitO(retries, committedRefs, descExtensions)
  }

  private[emcas] final def recordCycleDetected(bloomFilterSize: Int): Unit = {
    this.recordCycleDetectedO(bloomFilterSize)
  }

  private[choam] def getRetryStats(): Mcas.RetryStats = {
    Mcas.RetryStats(
      commits = this.getCommitsO(),
      retries = this.getRetriesO(),
      extensions = this.getExtensionsO(),
      mcasAttempts = this.getMcasAttemptsO(),
      committedRefs = this.getCommittedRefsO(),
      cyclesDetected = this.getCyclesDetectedO().toLong,
      maxRetries = this.getMaxRetriesO(),
      maxCommittedRefs = this.getMaxCommittedRefsO(),
      maxBloomFilterSize = this.getMaxBloomFilterSizeO(),
    )
  }

  private[choam] final override def maxReusedWeakRefs(): Int = {
    this.getMaxReuseEverO()
  }

  private[choam] final override def supportsStatistics: Boolean = {
    true
  }

  private[choam] final override def getStatisticsP(): Map[AnyRef, AnyRef] = {
    this._getStatisticsP()
  }

  private[choam] final override def getStatisticsO(): Map[AnyRef, AnyRef] = {
    this._getStatisticsO()
  }

  private[choam] final override def setStatisticsP(stats: Map[AnyRef, AnyRef]): Unit = {
    this._setStatisticsP(stats)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy