Skip to content

Commit

Permalink
Use a ConcurrentHashMap as a cache in ZEnvironment (zio#8992)
Browse files Browse the repository at this point in the history
* Use a `ConcurrentHashMap` as a cache in `ZEnvironment`

* Make code cross-compilable to Scala 2.12
  • Loading branch information
kyri-petrou authored Jul 3, 2024
1 parent 6d0cae1 commit 70aa22d
Showing 1 changed file with 30 additions and 16 deletions.
46 changes: 30 additions & 16 deletions core/shared/src/main/scala/zio/ZEnvironment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,36 @@ package zio

import zio.internal.UpdateOrderLinkedMap

import java.util.concurrent.ConcurrentHashMap
import scala.annotation.tailrec
import scala.collection.{immutable, mutable}
import scala.util.control.ControlThrowable
import scala.util.hashing.MurmurHash3

final class ZEnvironment[+R] private (
private val map: UpdateOrderLinkedMap[LightTypeTag, Any],
private val cache: mutable.HashMap[LightTypeTag, Any],
private val cache: ConcurrentHashMap[LightTypeTag, Any],
private val scope: Scope
) extends Serializable { self =>
import ZEnvironment.{ScopeTag, TaggedAny}
import ZEnvironment.{MissingService, ScopeTag, TaggedAny}

@deprecated("Kept for binary compatibility only. Do not use", "2.1.2")
private[ZEnvironment] def this(map: Map[LightTypeTag, Any], index: Int, cache: Map[LightTypeTag, Any] = Map.empty) =
this(UpdateOrderLinkedMap.fromMap(map), cache = mutable.HashMap.empty[LightTypeTag, Any] ++= cache, null)
this(UpdateOrderLinkedMap.fromMap(map), ZEnvironment.scalaToJucMap(cache), null)

@deprecated("Kept for binary compatibility only. Do not use", "2.1.5")
private[ZEnvironment] def this(
map: UpdateOrderLinkedMap[LightTypeTag, Any],
cache: immutable.HashMap[LightTypeTag, Any],
scope: Scope
) =
this(map, cache = mutable.HashMap.empty[LightTypeTag, Any] ++= cache, scope)
) = this(map, ZEnvironment.scalaToJucMap(cache), scope)

@deprecated("Kept for binary compatibility only. Do not use", "2.1.6")
private[ZEnvironment] def this(
map: UpdateOrderLinkedMap[LightTypeTag, Any],
cache: mutable.HashMap[LightTypeTag, Any],
scope: Scope
) = this(map, ZEnvironment.scalaToJucMap(cache), scope)

def ++[R1: EnvironmentTag](that: ZEnvironment[R1]): ZEnvironment[R with R1] =
self.union[R1](that)
Expand Down Expand Up @@ -79,9 +86,8 @@ final class ZEnvironment[+R] private (
def getDynamic[A](implicit tag: Tag[A]): Option[A] =
Option(unsafe.getOrElse(tag.tag, null.asInstanceOf[A])(Unsafe.unsafe))

override lazy val hashCode: Int = {
override lazy val hashCode: Int =
MurmurHash3.productHash((map, scope))
}

/**
* Prunes the environment to the set of services statically known to be
Expand Down Expand Up @@ -143,7 +149,7 @@ final class ZEnvironment[+R] private (

new ZEnvironment(
newMap,
cache = mutable.HashMap.empty[LightTypeTag, Any],
cache = new ConcurrentHashMap[LightTypeTag, Any],
scope = if (scopeTags.isEmpty) null else scope
)
}
Expand Down Expand Up @@ -184,7 +190,7 @@ final class ZEnvironment[+R] private (
}
val newScope = if (that.scope eq null) self.scope else that.scope
// Reuse the cache of the right hand-side
new ZEnvironment(newMap, cache = mutable.HashMap.empty ++= that.cache, scope = newScope)
new ZEnvironment(newMap, cache = new ConcurrentHashMap[LightTypeTag, Any](that.cache), scope = newScope)
}

/**
Expand Down Expand Up @@ -231,8 +237,8 @@ final class ZEnvironment[+R] private (
private[ZEnvironment] def addService[A](tag: LightTypeTag, a: A)(implicit
unsafe: Unsafe
): ZEnvironment[R with A] = {
val newCache = mutable.HashMap.empty[LightTypeTag, Any]
newCache.update(tag, a)
val newCache = new ConcurrentHashMap[LightTypeTag, Any]
newCache.put(tag, a)
new ZEnvironment(map.updated(tag, a), cache = newCache, scope = scope)
}

Expand All @@ -251,7 +257,7 @@ final class ZEnvironment[+R] private (
}

private[this] def getUnsafe[A](tag: LightTypeTag)(implicit unsafe: Unsafe): A = {
val fromCache = self.cache.getOrElse(tag, null)
val fromCache = self.cache.get(tag)
if (fromCache != null)
fromCache.asInstanceOf[A]
else if ((scope ne null) && isScopeTag(tag))
Expand All @@ -270,7 +276,7 @@ final class ZEnvironment[+R] private (
if (service == null) {
throw MissingService
} else {
self.cache.update(tag, service)
self.cache.put(tag, service)
service
}
}
Expand All @@ -280,8 +286,6 @@ final class ZEnvironment[+R] private (
unsafe: Unsafe
): ZEnvironment[R] =
add[A](tag, f(get(tag)))

private case object MissingService extends ControlThrowable
}
}

Expand Down Expand Up @@ -350,10 +354,20 @@ object ZEnvironment {
val empty: ZEnvironment[Any] =
new ZEnvironment[Any](
UpdateOrderLinkedMap.empty[LightTypeTag, Any],
cache = mutable.HashMap.empty[LightTypeTag, Any],
cache = new ConcurrentHashMap[LightTypeTag, Any],
scope = null
)

private case object MissingService extends ControlThrowable

// Can't use scala -> java collection conversions because they don't cross compile to Scala 2.12.
@deprecated("Marked as deprecated to avoid usage in non-deprecated methods", "2.1.16")
private def scalaToJucMap[K, V](map: collection.Map[K, V]): ConcurrentHashMap[K, V] = {
val jucMap = new ConcurrentHashMap[K, V]
map.foreach { case (k, v) => jucMap.put(k, v) }
jucMap
}

/**
* A `Patch[In, Out]` describes an update that transforms a `ZEnvironment[In]`
* to a `ZEnvironment[Out]` as a data structure. This allows combining updates
Expand Down

0 comments on commit 70aa22d

Please sign in to comment.