diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72f4bdf..54a7b8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,3 @@ on: jobs: test: uses: evolution-gaming/scala-github-actions/.github/workflows/ci.yml@dde27b9bd793d41d5aacf8fb74403c9de5da1146 # v6.3.0 - with: - scala_versions: '["2.13.18", "3.3.7"]' - # TODO sbt-scalafmt is in plugins.sbt but there is no .scalafmt.conf, so formatting was never checked - scalafmt_check: false diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..d31bdbb --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,96 @@ +# Main goals: +# - nicer commit diffs (trailing commas, no alignment for pattern matching, force new lines) +# - better interop with default IntelliJ IDEA setup (matching import and modifiers sorting logic) +# - better developer experience on laptop screens (like 16' MBPs) with IntelliJ IDEA (line wraps) + +version = 3.11.5 + +runner.dialect = scala213source3 + +# only format files tracked by git +project.git = true + +maxColumn = 120 +trailingCommas = always + +preset = default +# do not align to make nicer commit diffs +align.preset = none + +indent { + # altering defnSite and extendSite to have this: + # final class MyErr extends RuntimeException( + # "super error message", + # ) + # instead of this: + # final class MyErr extends RuntimeException( + # "super error message", + # ) + defnSite = 2 + extendSite = 0 +} + +spaces { + # makes string interpolation with curlies more visually distinct + inInterpolatedStringCurlyBraces = true +} + +newlines { + # keep author new lines where possible + source = keep + # force new line after "(implicit" for multi-line arg lists + implicitParamListModifierForce = [after] + avoidForSimpleOverflow = [ + tooLong, # if the line would be too long even after newline inserted, do nothing + slc, # do nothing if overflow caused by single line comment + ] +} + +verticalMultiline { + atDefnSite = true + arityThreshold = 4 # more than 3 args in a list will be turned vertical + newlineAfterOpenParen = true # for nicer commit diffs +} + +# for nicer commit diffs - forces new line before last parenthesis: +# class MyCls( +# arg1: String, +# arg2: String, +# ) extends MyTrait { +# +# without it: +# class MyCls( +# arg1: String, +# arg2: String) extends MyTrait { +danglingParentheses.exclude = [] + +docstrings { + # easier to view diffs in IDEA on 16' MBP screen if docs max line are shorter than code + wrapMaxColumn = 100 + # next settings make it similar to the default IDEA javadoc formatting + style = Asterisk + oneline = unfold + blankFirstLine = unfold +} + +rewrite.rules = [ + Imports, + RedundantParens, + SortModifiers, + prefercurlyfors, +] + +# put visibility modifier first +rewrite.sortModifiers.preset = styleGuide + +# Import sorting as similar as possible to scalafix's "OrganizeImports.preset = INTELLIJ_2020_3". +# Scalafix is not used as its commands mess up "all .." build aliases and it takes long time to run, +# while its code semantic based features are not needed here. +# I.e. detection of unused imports is done with Scala compiler options. +rewrite.imports { + sort = ascii + groups = [ + [".*"], + ["java\\..*", "javax\\..*", "scala\\..*"], + ] +} diff --git a/build.sbt b/build.sbt index c77e8f9..044a40f 100644 --- a/build.sbt +++ b/build.sbt @@ -4,15 +4,15 @@ name := "pubsub" organization := "com.evolutiongaming" -homepage := Some(url("https://github.com/evolution-gaming/pubsub")) +homepage := Some(uri("https://github.com/evolution-gaming/pubsub")) startYear := Some(2017) organizationName := "Evolution" -organizationHomepage := Some(url("https://evolution.com")) +organizationHomepage := Some(uri("https://evolution.com")) -crossScalaVersions := Seq("2.13.18", "3.3.7") +crossScalaVersions := Seq("2.13.18", "3.3.8") scalaVersion := crossScalaVersions.value.head @@ -40,7 +40,7 @@ libraryDependencies ++= crossSettings( if3 = Seq(Scodec.Scala3.Core), ) -licenses := Seq(("MIT", url("https://opensource.org/licenses/MIT"))) +licenses := Seq(("MIT", uri("https://opensource.org/licenses/MIT"))) scalacOptions ++= Seq( "-release:17", @@ -69,9 +69,11 @@ scalacOptions ++= crossSettings( Compile / doc / scalacOptions ++= Seq("-groups", "-implicits", "-no-link-warnings") -//addCommandAlias("check", "all versionPolicyCheck Compile/doc") -addCommandAlias("check", "show version") -addCommandAlias("build", "+all compile test") +versionPolicyIntention := Compatibility.BinaryCompatible + +addCommandAlias("check", "all scalafmtCheckRepo versionPolicyCheck Compile/doc") +addCommandAlias("fmt", "scalafmtRepo") +addCommandAlias("build", "+all compile testFull") def crossSettings[T](scalaVersion: String, if3: T, if2: T): T = { scalaVersion match { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index effca7f..4fabe1a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,17 +3,17 @@ import sbt._ object Dependencies { val `akka-serialization` = "com.evolutiongaming" %% "akka-serialization" % "1.1.0" - val `metric-tools` = "com.evolutiongaming" %% "metric-tools" % "3.0.0" - val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.12.2" - val scache = "com.evolution" %% "scache" % "6.0.1" - val scalatest = "org.scalatest" %% "scalatest" % "3.2.20" + val `metric-tools` = "com.evolutiongaming" %% "metric-tools" % "3.0.0" + val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.12.2" + val scache = "com.evolution" %% "scache" % "6.0.1" + val scalatest = "org.scalatest" %% "scalatest" % "3.2.20" object Akka { private val version = "2.6.21" - val Actor = "com.typesafe.akka" %% "akka-actor" % version + val Actor = "com.typesafe.akka" %% "akka-actor" % version val ClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % version - val Testkit = "com.typesafe.akka" %% "akka-testkit" % version - val Stream = "com.typesafe.akka" %% "akka-stream" % version + val Testkit = "com.typesafe.akka" %% "akka-testkit" % version + val Stream = "com.typesafe.akka" %% "akka-stream" % version } object Scodec { @@ -27,7 +27,7 @@ object Dependencies { } object Cats { - val core = "org.typelevel" %% "cats-core" % "2.13.0" + val core = "org.typelevel" %% "cats-core" % "2.13.0" val effect = "org.typelevel" %% "cats-effect" % "3.7.0" } } diff --git a/project/build.properties b/project/build.properties index 6d67d74..d3efb24 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.12.12 \ No newline at end of file +sbt.version = 2.0.6 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index 0dfc38f..54e584d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,10 +4,8 @@ addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1") addSbtPlugin("com.evolution" % "sbt-scalac-opts-plugin" % "0.2.0") -addSbtPlugin("com.evolution" % "sbt-artifactory-plugin" % "0.0.2") +addSbtPlugin("com.evolution" % "sbt-artifactory-plugin" % "0.1.2") -addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.2.1") +addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.3.0") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1") - -addDependencyTreePlugin +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.2") diff --git a/src/main/scala-2/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala b/src/main/scala-2/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala index 13a3b76..d5b0e38 100644 --- a/src/main/scala-2/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala +++ b/src/main/scala-2/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala @@ -1,12 +1,12 @@ package com.evolutiongaming.cluster.pubsub -import java.io.NotSerializableException - import akka.serialization.SerializerWithStringManifest import com.evolutiongaming.serialization.SerializedMsg import scodec.bits.ByteVector import scodec.codecs._ +import java.io.NotSerializableException + class PubSubSerializer extends SerializerWithStringManifest { import PubSubSerializer._ diff --git a/src/main/scala-3/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala b/src/main/scala-3/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala index 8110215..c10f310 100644 --- a/src/main/scala-3/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala +++ b/src/main/scala-3/com/evolutiongaming/cluster/pubsub/PubSubSerializer.scala @@ -1,7 +1,5 @@ package com.evolutiongaming.cluster.pubsub -import java.io.NotSerializableException - import akka.serialization.SerializerWithStringManifest import com.evolutiongaming.serialization.SerializedMsg import scodec.* @@ -9,6 +7,8 @@ import scodec.Codec.* import scodec.bits.* import scodec.codecs.* +import java.io.NotSerializableException + class PubSubSerializer extends SerializerWithStringManifest { import PubSubSerializer._ diff --git a/src/main/scala/akka/cluster/pubsub/DistributedPubSubMediatorSerializing.scala b/src/main/scala/akka/cluster/pubsub/DistributedPubSubMediatorSerializing.scala index f8f74ad..7c5f638 100644 --- a/src/main/scala/akka/cluster/pubsub/DistributedPubSubMediatorSerializing.scala +++ b/src/main/scala/akka/cluster/pubsub/DistributedPubSubMediatorSerializing.scala @@ -18,14 +18,15 @@ import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} /** - * Override to be able to serialize/deserialize msgs on PubSub level to offload akka remoting transport. - * Serialization performed if there are remote subscribers, once per msg, disregarding the number of subscribers - * Deserialization performed on every remote node once per message, disregarding the number of subscribers as well. - */ + * Override to be able to serialize/deserialize msgs on PubSub level to offload akka remoting + * transport. Serialization performed if there are remote subscribers, once per msg, disregarding + * the number of subscribers Deserialization performed on every remote node once per message, + * disregarding the number of subscribers as well. + */ class DistributedPubSubMediatorSerializing( settings: DistributedPubSubSettings, serialize: String => Boolean, - metrics: PubSub.Metrics[Id] + metrics: PubSub.Metrics[Id], ) extends DistributedPubSubMediator(settings) with DistributedPubSubMediatorSerializing.StreamHelper { import DistributedPubSubMediatorSerializing._ @@ -89,10 +90,10 @@ class DistributedPubSubMediatorSerializing( } msg match { - case _: PubSubMsg => forward() - case _: SerializedMsg => forward() + case _: PubSubMsg => forward() + case _: SerializedMsg => forward() case x: AnyRef if serialize(topic) => serializeAndForward(x) - case _ => forward() + case _ => forward() } } else { forward() @@ -107,7 +108,8 @@ class DistributedPubSubMediatorSerializing( } private def ignoreOrSendToDeadLetters(msg: Any) = - if (settings.sendToDeadLettersWhenNoSubscribers) context.system.deadLetters ! DeadLetter(msg, sender(), context.self) + if (settings.sendToDeadLettersWhenNoSubscribers) context.system.deadLetters ! + DeadLetter(msg, sender(), context.self) case class SerializationTask(topic: String, serialize: Future[(SendToAll, ActorRef)]) } @@ -119,7 +121,7 @@ object DistributedPubSubMediatorSerializing { def props( settings: DistributedPubSubSettings, serialize: String => Boolean, - metrics: PubSub.Metrics[Id] + metrics: PubSub.Metrics[Id], ): Props = { def actor = new DistributedPubSubMediatorSerializing(settings, serialize, metrics) @@ -131,7 +133,7 @@ object DistributedPubSubMediatorSerializing { system: ActorSystem, serialize: String => Boolean, metrics: PubSub.Metrics[Id], - name: String = "distributedPubSubMediatorOverride" + name: String = "distributedPubSubMediatorOverride", ): ActorRef = { val settings = DistributedPubSubSettings(system) @@ -144,10 +146,8 @@ object DistributedPubSubMediatorSerializing { system.asInstanceOf[ExtendedActorSystem].systemActorOf(props, name) } - private def toTopic(path: String) = path.split("/").last - class TopicSerializing( emptyTimeToLive: FiniteDuration, routingLogic: RoutingLogic, @@ -203,7 +203,6 @@ object DistributedPubSubMediatorSerializing { } } - trait StreamHelper extends Actor with ActorLogging { def selfSink[A]: Sink[(A, ActorRef), Future[Done]] = { @@ -212,14 +211,19 @@ object DistributedPubSubMediatorSerializing { implicit class SourceQueueWithCompleteOps[A](self: SourceQueueWithComplete[A]) { - def offerAndLog(elem: A, errorMsg: => String)(implicit ec: ExecutionContext): Unit = { + def offerAndLog( + elem: A, + errorMsg: => String, + )(implicit + ec: ExecutionContext, + ): Unit = { self.offer(elem) onComplete { - case Success(QueueOfferResult.Enqueued) => + case Success(QueueOfferResult.Enqueued) => case Success(QueueOfferResult.Failure(failure)) => log.error(failure, errorMsg) - case Success(failure) => log.error(s"$errorMsg $failure") - case Failure(failure) => log.error(failure, s"$errorMsg $failure") + case Success(failure) => log.error(s"$errorMsg $failure") + case Failure(failure) => log.error(failure, s"$errorMsg $failure") } } } } -} \ No newline at end of file +} diff --git a/src/main/scala/com/evolutiongaming/cluster/pubsub/LocalPubSub.scala b/src/main/scala/com/evolutiongaming/cluster/pubsub/LocalPubSub.scala index 8a1cf52..92cb91a 100644 --- a/src/main/scala/com/evolutiongaming/cluster/pubsub/LocalPubSub.scala +++ b/src/main/scala/com/evolutiongaming/cluster/pubsub/LocalPubSub.scala @@ -63,4 +63,4 @@ object LocalPubSub { private[cluster] case object GetState private[cluster] final case class State(value: Map[String, Set[ActorRef]]) -} \ No newline at end of file +} diff --git a/src/main/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribe.scala b/src/main/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribe.scala index 1a3ae6c..7bde4f9 100644 --- a/src/main/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribe.scala +++ b/src/main/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribe.scala @@ -4,16 +4,17 @@ import cats.Parallel import cats.data.{NonEmptyList => Nel} import cats.effect.{Concurrent, MonadCancelThrow, Resource} import cats.syntax.all._ +import com.evolution.scache.SerialMap import com.evolutiongaming.catshelper.ParallelHelper._ import com.evolutiongaming.catshelper.Runtime import com.evolutiongaming.cluster.pubsub.PubSub.OnMsg -import com.evolution.scache.SerialMap trait OptimiseSubscribe[F[_]] { def apply[A: Topic]( - onMsg: OnMsg[F, A])( - subscribe: OnMsg[F, A] => Resource[F, Unit] + onMsg: OnMsg[F, A], + )( + subscribe: OnMsg[F, A] => Resource[F, Unit], ): Resource[F, Unit] } @@ -22,18 +23,17 @@ object OptimiseSubscribe { def empty[F[_]]: OptimiseSubscribe[F] = new OptimiseSubscribe[F] { def apply[A: Topic]( - onMsg: OnMsg[F, A])( - subscribe: OnMsg[F, A] => Resource[F, Unit] + onMsg: OnMsg[F, A], + )( + subscribe: OnMsg[F, A] => Resource[F, Unit], ) = { subscribe(onMsg) } } - type Listener[F[_]] = OnMsg[F, Any] - - def of[F[_] : Concurrent: Runtime: Parallel]: F[OptimiseSubscribe[F]] = { + def of[F[_]: Concurrent: Runtime: Parallel]: F[OptimiseSubscribe[F]] = { for { serialMap <- SerialMap.of[F, String, Subscription[F]] } yield { @@ -41,14 +41,17 @@ object OptimiseSubscribe { } } - def apply[F[_] : MonadCancelThrow: Parallel](serialMap: SerialMap[F, String, Subscription[F]]): OptimiseSubscribe[F] = { + def apply[F[_]: MonadCancelThrow: Parallel](serialMap: SerialMap[F, String, Subscription[F]]) + : OptimiseSubscribe[F] = { new OptimiseSubscribe[F] { def apply[A]( - onMsg: OnMsg[F, A])( - subscribe: OnMsg[F, A] => Resource[F, Unit])(implicit - topic: Topic[A] + onMsg: OnMsg[F, A], + )( + subscribe: OnMsg[F, A] => Resource[F, Unit], + )(implicit + topic: Topic[A], ) = { val listener = onMsg.asInstanceOf[Listener[F]] @@ -61,7 +64,7 @@ object OptimiseSubscribe { val onMsg: OnMsg[F, A] = (a: A, sender) => { for { subscription <- serialMap.get(topic.name) - _ <- subscription.foldMapM { _.listeners.parFoldMap1 { listener => listener(a, sender) } } + _ <- subscription.foldMapM { _.listeners.parFoldMap1 { listener => listener(a, sender) } } } yield {} } @@ -88,11 +91,11 @@ object OptimiseSubscribe { } yield { val unsubscribe = for { _ <- update { - case None => none[Subscription[F]].pure[F] + case None => none[Subscription[F]].pure[F] case Some(subscription) => subscription - listener match { - case Some(subscription) => subscription.some.pure[F] - case None => subscription.unsubscribe as none[Subscription[F]] - } + case Some(subscription) => subscription.some.pure[F] + case None => subscription.unsubscribe as none[Subscription[F]] + } } } yield {} ((), unsubscribe) @@ -103,10 +106,9 @@ object OptimiseSubscribe { } } - final case class Subscription[F[_]]( unsubscribe: F[Unit], - listeners: Nel[Listener[F]] + listeners: Nel[Listener[F]], ) { self => def +(listener: Listener[F]): Subscription[F] = { diff --git a/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSub.scala b/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSub.scala index 59833c6..da7d94f 100644 --- a/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSub.scala +++ b/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSub.scala @@ -2,7 +2,7 @@ package com.evolutiongaming.cluster.pubsub import akka.actor.{Actor, ActorPath, ActorRef, ActorRefFactory, ActorSystem, Props} import akka.cluster.Cluster -import akka.cluster.pubsub.{DistributedPubSubMediatorSerializing, DistributedPubSubMediator => Mediator} +import akka.cluster.pubsub.{DistributedPubSubMediator => Mediator, DistributedPubSubMediatorSerializing} import akka.pattern._ import akka.util.Timeout import cats.effect.{Resource, Sync} @@ -21,15 +21,16 @@ import scala.reflect.ClassTag trait PubSub[F[_]] { import PubSub._ - def publish[A: Topic : ToBytes]( + def publish[A: Topic: ToBytes]( msg: A, sender: Option[ActorRef] = None, - sendToEachGroup: Boolean = false + sendToEachGroup: Boolean = false, ): F[Unit] - def subscribe[A: Topic : FromBytes : ClassTag]( - group: Option[String] = None)( - onMsg: OnMsg[F, A] + def subscribe[A: Topic: FromBytes: ClassTag]( + group: Option[String] = None, + )( + onMsg: OnMsg[F, A], ): Resource[F, Unit] def topics(timeout: FiniteDuration = 3.seconds): F[Set[String]] @@ -39,17 +40,15 @@ object PubSub { type OnMsg[F[_], -A] = (A, ActorPath) => F[Unit] - - def empty[F[_] : Applicative]: PubSub[F] = const(Set.empty[String].pure[F], ().pure[F]) - + def empty[F[_]: Applicative]: PubSub[F] = const(Set.empty[String].pure[F], ().pure[F]) def const[F[_]](topics: F[Set[String]], unit: F[Unit]): PubSub[F] = { val topics1 = topics new PubSub[F] { - def publish[A: Topic : ToBytes](msg: A, sender: Option[ActorRef], sendToEachGroup: Boolean) = unit + def publish[A: Topic: ToBytes](msg: A, sender: Option[ActorRef], sendToEachGroup: Boolean) = unit - def subscribe[A: Topic : FromBytes : ClassTag](group: Option[String])(onMsg: OnMsg[F, A]) = { + def subscribe[A: Topic: FromBytes: ClassTag](group: Option[String])(onMsg: OnMsg[F, A]) = { Resource.eval(unit) } @@ -57,14 +56,13 @@ object PubSub { } } - /** * Initializes a cluster-local pubsub. If cluster is not initialized, starts a node-local pubsub. */ - def of[F[_] : Sync : ToTry : ToFuture : FromFuture : LogOf]( + def of[F[_]: Sync: ToTry: ToFuture: FromFuture: LogOf]( system: ActorSystem, metrics: Metrics[F], - serialize: String => Boolean = _ => false + serialize: String => Boolean = _ => false, ): Resource[F, PubSub[F]] = { val toTry: F ~> Id = new (F ~> Id) { def apply[A](fa: F[A]): A = fa.toTry.get @@ -75,7 +73,7 @@ object PubSub { val metrics1 = metrics.mapK(toTry) val actorRef = Sync[F].delay { if (hasCluster) DistributedPubSubMediatorSerializing(system, serialize, metrics1) - else system.actorOf(LocalPubSub.props) + else system.actorOf(LocalPubSub.props) } Resource.make(actorRef) { ref => Sync[F].delay { system.stop(ref) } } } @@ -85,9 +83,16 @@ object PubSub { } } - private class PubSubCluster[F[_]: Sync: ToFuture: FromFuture](pubSub: ActorRef, log: Log[F], factory: ActorRefFactory) extends PubSub[F] { - override def publish[A](msg: A, sender: Option[ActorRef] = None, sendToEachGroup: Boolean = false) - (implicit topic: Topic[A], toBytes: ToBytes[A]): F[Unit] = { + private class PubSubCluster[F[_]: Sync: ToFuture: FromFuture](pubSub: ActorRef, log: Log[F], factory: ActorRefFactory) + extends PubSub[F] { + override def publish[A]( + msg: A, + sender: Option[ActorRef] = None, + sendToEachGroup: Boolean = false, + )(implicit + topic: Topic[A], + toBytes: ToBytes[A], + ): F[Unit] = { val toBytesAble = ToBytesAble(msg)(toBytes.apply) val publish = Mediator.Publish(topic.name, toBytesAble, sendToEachGroup) @@ -97,21 +102,28 @@ object PubSub { } yield () } - override def subscribe[A](group: Option[String] = None)(onMsg: OnMsg[F, A]) - (implicit topic: Topic[A], fromBytes: FromBytes[A], tag: ClassTag[A]): Resource[F, Unit] = { + override def subscribe[A]( + group: Option[String] = None, + )( + onMsg: OnMsg[F, A], + )(implicit + topic: Topic[A], + fromBytes: FromBytes[A], + tag: ClassTag[A], + ): Resource[F, Unit] = { val onToBytesAble: OnMsg[F, ToBytesAble] = (msg: ToBytesAble, sender: ActorPath) => { msg match { - case ToBytesAble.Bytes(bytes) => + case ToBytesAble.Bytes(bytes) => Sync[F].delay(fromBytes(bytes)).flatMap(onMsg(_, sender)) case ToBytesAble.Raw(tag(msg)) => onMsg(msg, sender) - case ToBytesAble.Raw(msg) => log.warn(s"$topic: receive unexpected $msg") + case ToBytesAble.Raw(msg) => log.warn(s"$topic: receive unexpected $msg") } } val logPrefixed = log.prefixed(topic.name) for { ref <- Resource.make(Sync[F].delay { val props = Props(new SubscriberActor(pubSub, group, topic.name)((msg, sender) => - onToBytesAble(msg, sender.path).toFuture + onToBytesAble(msg, sender.path).toFuture, )) factory.actorOf(props) }) { ref => @@ -132,16 +144,22 @@ object PubSub { } } - private class SubscriberActor(pubSub: ActorRef, group: Option[String], topic: String)(handler: (ToBytesAble, ActorRef) => Future[Unit]) extends Actor { + private class SubscriberActor( + pubSub: ActorRef, + group: Option[String], + topic: String, + )( + handler: (ToBytesAble, ActorRef) => Future[Unit], + ) extends Actor { import context.dispatcher override def receive: Receive = waitOn(Future.unit) private val log = akka.event.Logging(context.system, this) private def waitOn(future: Future[Unit]): Receive = { case _: Mediator.SubscribeAck => - log.debug(s"subscribed ${context.self}") + log.debug(s"subscribed ${ context.self }") case _: Mediator.UnsubscribeAck => - log.debug(s"unsubscribed ${context.self}") + log.debug(s"unsubscribed ${ context.self }") case msg: ToBytesAble => log.debug(s"receive $msg") val ref = sender() @@ -161,18 +179,18 @@ object PubSub { } } - def apply[F[_] : Sync : ToFuture : FromFuture](pubSub: ActorRef, log: Log[F], factory: ActorRefFactory): PubSub[F] = { + def apply[F[_]: Sync: ToFuture: FromFuture](pubSub: ActorRef, log: Log[F], factory: ActorRefFactory): PubSub[F] = { new PubSubCluster[F](pubSub, log, factory) } - def proxy[F[_] : Sync : FromFuture](actorRef: ActorRef): PubSub[F] = new PubSub[F] { + def proxy[F[_]: Sync: FromFuture](actorRef: ActorRef): PubSub[F] = new PubSub[F] { - def publish[A: Topic : ToBytes](msg: A, sender: Option[ActorRef] = None, sendToEachGroup: Boolean = false) = { + def publish[A: Topic: ToBytes](msg: A, sender: Option[ActorRef] = None, sendToEachGroup: Boolean = false) = { val sender1 = sender getOrElse ActorRef.noSender Sync[F].delay { actorRef.tell(msg, sender1) } } - def subscribe[A: Topic : FromBytes : ClassTag](group: Option[String] = None)(onMsg: OnMsg[F, A]) = { + def subscribe[A: Topic: FromBytes: ClassTag](group: Option[String] = None)(onMsg: OnMsg[F, A]) = { Resource.pure[F, Unit](()) } @@ -186,7 +204,6 @@ object PubSub { } } - @deprecated("Not used anymore", "10.0.0") object Subscription { sealed trait In[+A] @@ -197,7 +214,6 @@ object PubSub { } } - trait Metrics[F[_]] { def subscribe(topic: String): F[Unit] @@ -215,8 +231,7 @@ object PubSub { object Metrics { - def empty[F[_] : Applicative]: Metrics[F] = const(().pure[F]) - + def empty[F[_]: Applicative]: Metrics[F] = const(().pure[F]) def const[F[_]](unit: F[Unit]): Metrics[F] = new Metrics[F] { @@ -233,8 +248,7 @@ object PubSub { def latency(topic: String, latencyMs: Long) = unit } - - def codahale[F[_] : Sync](registry: MetricRegistry): F[Metrics[F]] = { + def codahale[F[_]: Sync](registry: MetricRegistry): F[Metrics[F]] = { def nameOf(topic: String) = Sync[F].delay { MetricName(topic) } @@ -245,8 +259,8 @@ object PubSub { val latencyHistogram = Sync[F].delay { registry.histogram("latency") } for { - toBytesMeter <- toBytesMeter - fromBytesMeter <- fromBytesMeter + toBytesMeter <- toBytesMeter + fromBytesMeter <- fromBytesMeter latencyHistogram <- latencyHistogram } yield { new Metrics[F] { @@ -254,52 +268,51 @@ object PubSub { def subscribe(topic: String) = { for { name <- nameOf(topic) - _ <- Sync[F].delay { registry.counter(s"$name.subscriptions").inc() } + _ <- Sync[F].delay { registry.counter(s"$name.subscriptions").inc() } } yield {} } def unsubscribe(topic: String) = { for { name <- nameOf(topic) - _ <- Sync[F].delay { registry.counter(s"$name.subscriptions").dec() } + _ <- Sync[F].delay { registry.counter(s"$name.subscriptions").dec() } } yield {} } def publish(topic: String) = { for { name <- nameOf(topic) - _ <- Sync[F].delay { registry.meter(s"$name.publish").mark() } + _ <- Sync[F].delay { registry.meter(s"$name.publish").mark() } } yield {} } def toBytes(topic: String, size: Long) = { for { name <- nameOf(topic) - _ <- Sync[F].delay { toBytesMeter.mark(size) } - _ <- Sync[F].delay { registry.meter(s"$name.toBytes").mark(size) } + _ <- Sync[F].delay { toBytesMeter.mark(size) } + _ <- Sync[F].delay { registry.meter(s"$name.toBytes").mark(size) } } yield {} } def fromBytes(topic: String, size: Long) = { for { name <- nameOf(topic) - _ <- Sync[F].delay { fromBytesMeter.mark(size) } - _ <- Sync[F].delay { registry.meter(s"$name.fromBytes").mark(size) } + _ <- Sync[F].delay { fromBytesMeter.mark(size) } + _ <- Sync[F].delay { registry.meter(s"$name.fromBytes").mark(size) } } yield {} } def latency(topic: String, latencyMs: Long) = { for { name <- nameOf(topic) - _ <- Sync[F].delay { latencyHistogram.update(latencyMs) } - _ <- Sync[F].delay { registry.histogram(s"$name.latency").update(latencyMs) } + _ <- Sync[F].delay { latencyHistogram.update(latencyMs) } + _ <- Sync[F].delay { registry.histogram(s"$name.latency").update(latencyMs) } } yield {} } } } } - implicit class MetricsOps[F[_]](val self: Metrics[F]) extends AnyVal { def mapK[G[_]](f: F ~> G): Metrics[G] = new Metrics[G] { @@ -319,17 +332,16 @@ object PubSub { } } - implicit class PubSubOps[F[_]](val self: PubSub[F]) extends AnyVal { def withOptimiseSubscribe(optimiseSubscribe: OptimiseSubscribe[F]): PubSub[F] = { new PubSub[F] { - def publish[A: Topic : ToBytes](msg: A, sender: Option[ActorRef], sendToEachGroup: Boolean) = { + def publish[A: Topic: ToBytes](msg: A, sender: Option[ActorRef], sendToEachGroup: Boolean) = { self.publish(msg, sender, sendToEachGroup) } - def subscribe[A: Topic : FromBytes : ClassTag](group: Option[String])(onMsg: OnMsg[F, A]) = { + def subscribe[A: Topic: FromBytes: ClassTag](group: Option[String])(onMsg: OnMsg[F, A]) = { optimiseSubscribe[A](onMsg) { onMsg => self.subscribe[A](group)(onMsg) } @@ -339,17 +351,21 @@ object PubSub { } } - - def withMetrics(metrics: Metrics[F])(implicit F: Monad[F]): PubSub[F] = { + def withMetrics( + metrics: Metrics[F], + )(implicit + F: Monad[F], + ): PubSub[F] = { new PubSub[F] { def publish[A]( msg: A, sender: Option[ActorRef] = None, - sendToEachGroup: Boolean = false)(implicit + sendToEachGroup: Boolean = false, + )(implicit topic: Topic[A], - toBytes: ToBytes[A] + toBytes: ToBytes[A], ) = { for { a <- self.publish(msg, sender, sendToEachGroup) @@ -358,11 +374,13 @@ object PubSub { } def subscribe[A]( - group: Option[String] = None)( - onMsg: OnMsg[F, A])(implicit + group: Option[String] = None, + )( + onMsg: OnMsg[F, A], + )(implicit topic: Topic[A], fromBytes: FromBytes[A], - tag: ClassTag[A] + tag: ClassTag[A], ) = { val name = topic.name diff --git a/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSubMsg.scala b/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSubMsg.scala index 38bb52f..ea9d78b 100644 --- a/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSubMsg.scala +++ b/src/main/scala/com/evolutiongaming/cluster/pubsub/PubSubMsg.scala @@ -2,4 +2,4 @@ package com.evolutiongaming.cluster.pubsub import com.evolutiongaming.serialization.SerializedMsg -final case class PubSubMsg(serializedMsg: SerializedMsg, timestamp: Long) \ No newline at end of file +final case class PubSubMsg(serializedMsg: SerializedMsg, timestamp: Long) diff --git a/src/main/scala/com/evolutiongaming/cluster/pubsub/ToBytes.scala b/src/main/scala/com/evolutiongaming/cluster/pubsub/ToBytes.scala index 414ad1c..3d1950c 100644 --- a/src/main/scala/com/evolutiongaming/cluster/pubsub/ToBytes.scala +++ b/src/main/scala/com/evolutiongaming/cluster/pubsub/ToBytes.scala @@ -14,7 +14,6 @@ object ToBytes { implicit val BytesToBytes: ToBytes[ByteVector] = (value: ByteVector) => value } - trait FromBytes[A] { def apply(bytes: ByteVector): A } diff --git a/src/main/scala/com/evolutiongaming/cluster/pubsub/Topic.scala b/src/main/scala/com/evolutiongaming/cluster/pubsub/Topic.scala index b431c34..1e5932a 100644 --- a/src/main/scala/com/evolutiongaming/cluster/pubsub/Topic.scala +++ b/src/main/scala/com/evolutiongaming/cluster/pubsub/Topic.scala @@ -5,5 +5,8 @@ import scala.reflect.ClassTag final case class Topic[-A](name: String) object Topic { - def apply[A](implicit tag: ClassTag[A]): Topic[A] = apply(tag.runtimeClass.getName) -} \ No newline at end of file + def apply[A]( + implicit + tag: ClassTag[A], + ): Topic[A] = apply(tag.runtimeClass.getName) +} diff --git a/src/test/scala/com/evolutiongaming/cluster/pubsub/ActorSpec.scala b/src/test/scala/com/evolutiongaming/cluster/pubsub/ActorSpec.scala index d811b34..db254e8 100644 --- a/src/test/scala/com/evolutiongaming/cluster/pubsub/ActorSpec.scala +++ b/src/test/scala/com/evolutiongaming/cluster/pubsub/ActorSpec.scala @@ -18,4 +18,3 @@ trait ActorSpec extends BeforeAndAfterAll { abstract class ActorScope extends TestKit(actorSystem) with ImplicitSender with DefaultTimeout } - diff --git a/src/test/scala/com/evolutiongaming/cluster/pubsub/IOSuite.scala b/src/test/scala/com/evolutiongaming/cluster/pubsub/IOSuite.scala index 88c5020..14548dd 100644 --- a/src/test/scala/com/evolutiongaming/cluster/pubsub/IOSuite.scala +++ b/src/test/scala/com/evolutiongaming/cluster/pubsub/IOSuite.scala @@ -1,7 +1,7 @@ package com.evolutiongaming.cluster.pubsub -import cats.effect.{Clock, IO} import cats.effect.unsafe.implicits.global +import cats.effect.{Clock, IO} import com.evolutiongaming.catshelper.MeasureDuration import org.scalatest.Succeeded @@ -22,4 +22,4 @@ object IOSuite { implicit class IOOps[A](val self: IO[A]) extends AnyVal { def run(timeout: FiniteDuration = Timeout): Future[Succeeded.type] = runIO(self, timeout) } -} \ No newline at end of file +} diff --git a/src/test/scala/com/evolutiongaming/cluster/pubsub/LocalPubSubSpec.scala b/src/test/scala/com/evolutiongaming/cluster/pubsub/LocalPubSubSpec.scala index 806cb1e..f9ae38b 100644 --- a/src/test/scala/com/evolutiongaming/cluster/pubsub/LocalPubSubSpec.scala +++ b/src/test/scala/com/evolutiongaming/cluster/pubsub/LocalPubSubSpec.scala @@ -4,11 +4,11 @@ import akka.cluster.pubsub.{DistributedPubSubMediator => Mediator} import akka.testkit.{TestActorRef, TestProbe} import com.evolutiongaming.cluster.pubsub.LocalPubSub._ import org.scalatest.concurrent.Eventually - -import scala.concurrent.duration._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import scala.concurrent.duration._ + class LocalPubSubSpec extends AnyFlatSpec with ActorSpec with Matchers with Eventually { "LocalPubSub" should "receive Subscribe" in new Scope { diff --git a/src/test/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribeSpec.scala b/src/test/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribeSpec.scala index f6cdce5..bdfa331 100644 --- a/src/test/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribeSpec.scala +++ b/src/test/scala/com/evolutiongaming/cluster/pubsub/OptimiseSubscribeSpec.scala @@ -31,15 +31,16 @@ class OptimiseSubscribeSpec extends AsyncFunSuite with ActorSpec with Matchers { sender.use { sender => for { optimiseSubscribe <- OptimiseSubscribe.of[F] - msgsRef <- Ref[F].of(Set.empty[Msg]) - listenersRef <- Ref[F].of(List.empty[OnMsg[F, Msg]]) - publish = (msg: Msg) => for { - listeners <- listenersRef.get - _ <- listeners.foldMapM { onMsg => onMsg(msg, sender.path) } + msgsRef <- Ref[F].of(Set.empty[Msg]) + listenersRef <- Ref[F].of(List.empty[OnMsg[F, Msg]]) + publish = (msg: Msg) => + for { + listeners <- listenersRef.get + _ <- listeners.foldMapM { onMsg => onMsg(msg, sender.path) } - } yield {} + } yield {} - subscribe = (prefix: String) => { + subscribe = (prefix: String) => { val onMsg = (msg: Msg, _: ActorPath) => msgsRef.update { _ + s"$prefix-$msg" } val subscribe = (onMsg: OnMsg[F, Msg]) => { val result = for { @@ -59,21 +60,21 @@ class OptimiseSubscribeSpec extends AsyncFunSuite with ActorSpec with Matchers { } } - listeners0 <- listenersRef.get - _ <- publish("0") - msgs0 <- msgsRef.get - unsubscribe0 <- subscribe("1") - listeners1 <- listenersRef.get - _ <- publish("1") - msgs1 <- msgsRef.get - unsubscribe1 <- subscribe("2") - listeners2 <- listenersRef.get - _ <- publish("2") - msgs2 <- msgsRef.get - _ <- unsubscribe0 - listeners3 <- listenersRef.get - _ <- unsubscribe1 - listeners4 <- listenersRef.get + listeners0 <- listenersRef.get + _ <- publish("0") + msgs0 <- msgsRef.get + unsubscribe0 <- subscribe("1") + listeners1 <- listenersRef.get + _ <- publish("1") + msgs1 <- msgsRef.get + unsubscribe1 <- subscribe("2") + listeners2 <- listenersRef.get + _ <- publish("2") + msgs2 <- msgsRef.get + _ <- unsubscribe0 + listeners3 <- listenersRef.get + _ <- unsubscribe1 + listeners4 <- listenersRef.get } yield { listeners0.size shouldEqual 0 msgs0 shouldEqual Set.empty[String] diff --git a/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSerializerSpec.scala b/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSerializerSpec.scala index 1639152..1315876 100644 --- a/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSerializerSpec.scala +++ b/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSerializerSpec.scala @@ -1,9 +1,9 @@ package com.evolutiongaming.cluster.pubsub import com.evolutiongaming.serialization.SerializedMsg -import scodec.bits.ByteVector import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import scodec.bits.ByteVector class PubSubSerializerSpec extends AnyFunSuite with Matchers { @@ -29,4 +29,4 @@ class PubSubSerializerSpec extends AnyFunSuite with Matchers { val deserialized = serializer.fromBinary(bytes, manifest) deserialized.asInstanceOf[A] } -} \ No newline at end of file +} diff --git a/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSpec.scala b/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSpec.scala index e06605e..06aedc1 100644 --- a/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSpec.scala +++ b/src/test/scala/com/evolutiongaming/cluster/pubsub/PubSubSpec.scala @@ -7,12 +7,11 @@ import cats.effect.unsafe.implicits.global import com.evolutiongaming.catshelper.CatsHelper._ import com.evolutiongaming.catshelper.LogOf import com.evolutiongaming.serialization.ToBytesAble - -import scala.concurrent.Await import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import scala.collection.mutable +import scala.concurrent.Await class PubSubSpec extends AnyWordSpec with ActorSpec with Matchers { @@ -28,7 +27,9 @@ class PubSubSpec extends AnyWordSpec with ActorSpec with Matchers { } { s"subscribe, group: $group" in new Scope { val msgs = mutable.ArrayBuffer[String]() - val (_, unsubscribe) = pubSub.subscribe[Msg](group) { (msg: Msg, _) => IO(msgs.addOne(msg)) }.allocated.toTry.get + val (_, unsubscribe) = pubSub.subscribe[Msg](group) { (msg: Msg, _) => + IO(msgs.addOne(msg)) + }.allocated.toTry.get val subscriber = expectMsgPF() { case Mediator.Subscribe(`topic`, `group`, ref) => ref } subscriber ! ToBytesAble.Raw("msg1")(ToBytes.StrToBytes.apply) @@ -68,4 +69,4 @@ class PubSubSpec extends AnyWordSpec with ActorSpec with Matchers { val log = LogOf.slf4j[IO].unsafeRunSync().apply(getClass).unsafeRunSync() val pubSub = PubSub[IO](testActor, log, system) } -} \ No newline at end of file +}