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

scala.reflect.runtime.SynchronizedSymbols.scala Maven / Gradle / Ivy

There is a newer version: 2.13.15
Show newest version
/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala
package reflect
package runtime

import scala.collection.immutable
import scala.reflect.internal.Flags._

private[reflect] trait SynchronizedSymbols extends internal.Symbols { self: SymbolTable =>

  private lazy val atomicIds = new java.util.concurrent.atomic.AtomicInteger(0)
  override protected def nextId() = atomicIds.incrementAndGet()

  @deprecated("Global existential IDs no longer used", "2.12.1")
  private lazy val atomicExistentialIds = new java.util.concurrent.atomic.AtomicInteger(0)
  @deprecated("Global existential IDs no longer used", "2.12.1")
  override protected def nextExistentialId() = atomicExistentialIds.incrementAndGet()

  private lazy val _recursionTable = mkThreadLocalStorage(immutable.Map.empty[Symbol, Int])
  override def recursionTable = _recursionTable.get
  override def recursionTable_=(value: immutable.Map[Symbol, Int]) = _recursionTable.set(value)

  // Set the fields which point companions at one another.  Returns the module.
  override def connectModuleToClass(m: ModuleSymbol, moduleClass: ClassSymbol): ModuleSymbol =
    gilSynchronized { super.connectModuleToClass(m, moduleClass) }

  override def newFreeTermSymbol(name: TermName, value: => Any, flags: Long = 0L, origin: String = null): FreeTermSymbol =
    new FreeTermSymbol(name, value, origin) with SynchronizedTermSymbol initFlags flags

  override def newFreeTypeSymbol(name: TypeName, flags: Long = 0L, origin: String = null): FreeTypeSymbol =
    new FreeTypeSymbol(name, origin) with SynchronizedTypeSymbol initFlags flags

  override protected def makeNoSymbol: NoSymbol = new NoSymbol with SynchronizedSymbol

  trait SynchronizedSymbol extends Symbol {

    /** (Things written in this comment only applies to runtime reflection. Compile-time reflection,
     *  especially across phases and runs, is somewhat more complicated, but we won't be touching it,
     *  because at the moment we only care about synchronizing runtime reflection).
     *
     *  As it has been noted on multiple occasions, generally speaking, reflection artifacts aren't thread-safe.
     *  Reasons for that differ from artifact to artifact. In some cases it's quite bad (e.g. types use a number
     *  of non-concurrent compiler caches, so we need to serialize certain operations on types in order to make
     *  sure that things stay deterministic). However, in case of symbols there's hope, because it's only during
     *  initialization that symbols are thread-unsafe. After everything's set up, symbols become immutable
     *  (sans a few deterministic caches that can be populated simultaneously by multiple threads) and therefore thread-safe.
     *
     *  Note that by saying "symbols become immutable" I mean literally that. In a very common case of PackageClassSymbol's,
     *  even when a symbol finishes its initialization and becomes immutable, its info forever remains mutable.
     *  Therefore even if we no longer need to synchronize a PackageClassSymbol after it's initialized, we still have to take
     *  care of its ClassInfoType (or, more precisely, of the underlying Scope), but that's done elsewhere, and
     *  here we don't need to worry about that.
     *
     *  Okay, so now we simply check `Symbol.isInitialized` and if it's true, then everything's fine? Haha, nope!
     *  The thing is that some completers call sym.setInfo when still in-flight and then proceed with initialization
     *  (e.g. see LazyPackageType). Consequently, setInfo sets _validTo to current period, which means that after
     *  a call to setInfo isInitialized will start returning true. Unfortunately, this doesn't mean that info becomes
     *  ready to be used, because subsequent initialization might change the info.
     *
     *  Therefore we need to somehow distinguish between initialized and really initialized symbol states.
     *  Okay, let's do it on per-completer basis. We have seven kinds of completers to worry about:
     *    1) LazyPackageType that initializes packages and their underlying package classes
     *    2) TopClassCompleter that initializes top-level Scala-based class-module companion pairs of static definitions
     *    3) LazyTypeRef and LazyTypeRefAndAlias set up by TopClassCompleter that initialize (transitive members) of top-level classes/modules
     *    4) FromJavaClassCompleter that does the same for both top-level and non-toplevel Java-based classes/modules
     *    5) Fully-initialized signatures of non-class/module Java-based reflection artifacts
     *    6) Importing completer that transfers metadata from one universe to another
     *    7) Signatures of special symbols such as roots and symbolsNotPresentInBytecode
     *
     *  The mechanisms underlying completion are quite complex, and it'd be only natural to suppose that over time we're going to overlook something.
     *  Wrt isThreadsafe we could have two wrong situations: false positives (isThreadsafe = true, but the symbol isn't actually threadsafe)
     *  and false negatives (isThreadsafe = false, but the symbol is actually threadsafe). However, even though both are wrong, only the former
     *  is actively malicious. Indeed, false positives might lead to races, inconsistent state and crashes, while the latter would only cause
     *  `initialize` to be called and a gil to be taken on every potentially auto-initializable operation. Unpleasant yes, but still robust.
     *
     *  What makes me hopeful is that:
     *    1) By default (e.g. if some new completion mechanism gets introduced for a special flavor of symbols and we forget to call markCompleted)
     *       isThreadsafe is always in false negative state, which is unpleasant but safe.
     *    2) Calls to `markCompleted` which are the only potential source of erroneous behavior are few and are relatively easy to place:
     *       just put them just before your completer's `complete` returns, and you should be fine.
     *
     *  upd. Actually, there's another problem of not keeping initialization mask up-to-date. If we're not careful enough,
     *  then it might so happen that getting a certain flag that the compiler assumes to be definitively set will spuriously
     *  return isThreadsafe(purpose = FlagsOp()) = false and that will lead to spurious auto-initialization,
     *  which will cause an SO or a cyclic reference or some other crash. I've done my best to go through all possible completers
     *  and call `markFlagsCompleted` where appropriate, but again over time something might be overlooked, so to guard against that
     *  I'm only considering TopLevelPickledFlags to be sources of potential initialization. This ensures that such system flags as
     *  isMethod, isModule or isPackage are never going to auto-initialize.
     */
    override def isThreadsafe(purpose: SymbolOps) = {
      if (isCompilerUniverse) false
      else if (_initialized) true
      else purpose.isFlagRelated && (_initializationMask & purpose.mask & TopLevelPickledFlags) == 0
    }

    override final def privateWithin: Symbol = {
      // See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
      if (!isCompilerUniverse && !isThreadsafe(purpose = AllOps)) initialize
      super.privateWithin
    }

    override def annotations: List[AnnotationInfo] = {
      // See `getFlag` to learn more about the `isThreadsafe` call in the body of this method.
      if (!isCompilerUniverse && !isThreadsafe(purpose = AllOps)) initialize
      super.annotations
    }

    /** Communicates with completers declared in scala.reflect.runtime.SymbolLoaders
     *  about the status of initialization of the underlying symbol.
     *
     *  Unfortunately, it's not as easy as just introducing the `markThreadsafe` method that would be called
     *  by the completers when they are really done (as opposed to `setInfo` that, as mentioned above, doesn't mean anything).
     *
     *  Since we also want to auto-initialize symbols when certain methods are being called (`Symbol.hasFlag` for example),
     *  we need to track the identity of the initializer, so as to block until initialization is complete if the caller
     *  comes from a different thread, but to skip auto-initialization if we're the initializing thread.
     *
     *  Just a volatile var is fine, because:
     *    1) Status can only be changed in a single-threaded fashion (this is enforced by gilSynchronized
     *       that effectively guards `Symbol.initialize`), which means that there can't be update conflicts.
     *    2) If someone reads a stale value of status, then the worst thing that might happen is that this someone
     *       is going to spuriously call `initialize`, which is either a gil-protected operation (if the symbol isn't initialized yet)
     *       or a no-op (if the symbol is already initialized), and that is fine in both cases.
     *
     *  upd. It looks like we also need to keep track of a mask of initialized flags to make sure
     *  that normal symbol initialization routines don't trigger auto-init in Symbol.flags-related routines (e.g. Symbol.getFlag).
     *  Due to the same reasoning as above, a single volatile var is enough for to store the mask.
     */
    @volatile private[this] var _initialized = false
    @volatile private[this] var _initializationMask = TopLevelPickledFlags
    override def markFlagsCompleted(mask: Long): this.type = { _initializationMask = _initializationMask & ~mask; this }
    override def markAllCompleted(): this.type = { _initializationMask = 0L; _initialized = true; this }

    @inline final def gilSynchronizedIfNotThreadsafe[T](body: => T): T = {
      // TODO: debug and fix the race that doesn't allow us uncomment this optimization
      // if (isCompilerUniverse || isThreadsafe(purpose = AllOps)) body
      // else gilSynchronized { body }
      gilSynchronized { body }
    }

    override final def getFlag(mask: Long): Long = {
      if (!isCompilerUniverse && !isThreadsafe(purpose = FlagOps(mask))) initialize
      super.getFlag(mask)
    }

    override def validTo = gilSynchronizedIfNotThreadsafe { super.validTo }
    override def info = gilSynchronizedIfNotThreadsafe { super.info }
    override def rawInfo: Type = gilSynchronizedIfNotThreadsafe { super.rawInfo }
    override def exists: Boolean = gilSynchronizedIfNotThreadsafe(super.exists)
    override def typeSignature: Type = gilSynchronizedIfNotThreadsafe { super.typeSignature }
    override def typeSignatureIn(site: Type): Type = gilSynchronizedIfNotThreadsafe { super.typeSignatureIn(site) }
    override def typeConstructor: Type = gilSynchronizedIfNotThreadsafe { super.typeConstructor }

    override def typeParams: List[Symbol] = gilSynchronizedIfNotThreadsafe {
      if (isCompilerUniverse) super.typeParams
      else {
        def completeTypeParams = {
          if (isMonomorphicType) Nil
          else {
            // analogously to the "info" getter, here we allow for two completions:
            //   one: sourceCompleter to LazyType, two: LazyType to completed type
            rawInfo load this
            if (validTo == NoPeriod)
              rawInfo load this

            rawInfo.typeParams
          }
        }
        if (validTo != NoPeriod) rawInfo.typeParams else completeTypeParams
      }
    }
    override def unsafeTypeParams: List[Symbol] = gilSynchronizedIfNotThreadsafe {
      if (isCompilerUniverse) super.unsafeTypeParams
      else {
        if (isMonomorphicType) Nil
        else rawInfo.typeParams
      }
    }

// ------ creators -------------------------------------------------------------------

    override protected def createAbstractTypeSymbol(name: TypeName, pos: Position, newFlags: Long): AbstractTypeSymbol =
      new AbstractTypeSymbol(this, pos, name) with SynchronizedTypeSymbol initFlags newFlags

    override protected def createAliasTypeSymbol(name: TypeName, pos: Position, newFlags: Long): AliasTypeSymbol =
      new AliasTypeSymbol(this, pos, name) with SynchronizedTypeSymbol initFlags newFlags

    override protected def createTypeSkolemSymbol(name: TypeName, origin: AnyRef, pos: Position, newFlags: Long): TypeSkolem =
      new TypeSkolem(this, pos, name, origin) with SynchronizedTypeSymbol initFlags newFlags

    override protected def createClassSymbol(name: TypeName, pos: Position, newFlags: Long): ClassSymbol =
      new ClassSymbol(this, pos, name) with SynchronizedClassSymbol initFlags newFlags

    override protected def createModuleClassSymbol(name: TypeName, pos: Position, newFlags: Long): ModuleClassSymbol =
      new ModuleClassSymbol(this, pos, name) with SynchronizedModuleClassSymbol initFlags newFlags

    override protected def createPackageClassSymbol(name: TypeName, pos: Position, newFlags: Long): PackageClassSymbol =
      new PackageClassSymbol(this, pos, name) with SynchronizedModuleClassSymbol initFlags newFlags

    override protected def createRefinementClassSymbol(pos: Position, newFlags: Long): RefinementClassSymbol =
      new RefinementClassSymbol(this, pos) with SynchronizedClassSymbol initFlags newFlags

    override protected def createPackageObjectClassSymbol(pos: Position, newFlags: Long): PackageObjectClassSymbol =
      new PackageObjectClassSymbol(this, pos) with SynchronizedClassSymbol initFlags newFlags

    override protected def createMethodSymbol(name: TermName, pos: Position, newFlags: Long): MethodSymbol =
      new MethodSymbol(this, pos, name) with SynchronizedMethodSymbol initFlags newFlags

    override protected def createModuleSymbol(name: TermName, pos: Position, newFlags: Long): ModuleSymbol =
      new ModuleSymbol(this, pos, name) with SynchronizedTermSymbol initFlags newFlags

    override protected def createPackageSymbol(name: TermName, pos: Position, newFlags: Long): ModuleSymbol =
      createModuleSymbol(name, pos, newFlags)

    override protected def createValueParameterSymbol(name: TermName, pos: Position, newFlags: Long) =
      new TermSymbol(this, pos, name) with SynchronizedTermSymbol initFlags newFlags

    override protected def createValueMemberSymbol(name: TermName, pos: Position, newFlags: Long) =
      new TermSymbol(this, pos, name) with SynchronizedTermSymbol initFlags newFlags
  }

// ------- subclasses ---------------------------------------------------------------------

  trait SynchronizedTermSymbol extends SynchronizedSymbol

  trait SynchronizedMethodSymbol extends MethodSymbol with SynchronizedTermSymbol

  trait SynchronizedModuleSymbol extends ModuleSymbol with SynchronizedTermSymbol

  trait SynchronizedTypeSymbol extends TypeSymbol with SynchronizedSymbol {
    // unlike with typeConstructor, a lock is necessary here, because tpe calculation relies on
    // temporarily assigning NoType to tpeCache to detect cyclic reference errors
    private lazy val tpeLock = new Object
    override def tpe_* : Type = gilSynchronizedIfNotThreadsafe { tpeLock.synchronized { super.tpe_* } }
  }

  trait SynchronizedClassSymbol extends ClassSymbol with SynchronizedTypeSymbol

  trait SynchronizedModuleClassSymbol extends ModuleClassSymbol with SynchronizedClassSymbol
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy