AKKA `Http().singleRequest` may stuck on one node - scala

Short story:
In a 2-node Kubernetes AKKA clustering setup, each node runs a piece of code that calls a Kubeless function as follows:
log.trace("Calling URL [{}] with bytes response.", address)
Http().singleRequest(
request = HttpRequest(
method = HttpMethods.POST,
uri = address,
entity = HttpEntity(contentType, data)
),
connectionContext = noCertificateCheckContext
)
The above code generates the following log on the good node:
Calling URL [http://10.233.54.59:8080] with bytes response.
[t-dispatcher-22] [36mr$$anonfun$receive$1[m: Creating pool.
[t-dispatcher-22] [36mr$$anonfun$receive$1[m: Dispatching request [POST Strict(14160 bytes)] to pool
[t-dispatcher-21] [36mr$$anonfun$receive$1[m: [0 (Unconnected)]Dispatching request [POST / Strict(14160 bytes)]
[t-dispatcher-21] [36mr$$anonfun$receive$1[m: [0 (Unconnected)]Before event [onNewRequest] In state [Unconnected] for [0 ms]
[t-dispatcher-21] [36mr$$anonfun$receive$1[m: [0 (Unconnected)]Establishing connection
[t-dispatcher-21] [36mr$$anonfun$receive$1[m: [0 (Connecting)]After event [onNewRequest] State change [Unconnected] -> [Connecting]
[t-dispatcher-14] [36mr$$anonfun$receive$1[m: Resolving 10.233.54.59 before connecting
[...]
...and each of these calls succeeds.
The above code generates the following code on the bad node:
Calling URL [http://10.233.54.59:8080] with bytes response.
...and literally nothing after that. The bad node can accept incoming requests through its HTTP API written in AKKA and can do all things, but not Http().singleRequest, which is used to call Kubeless functions.
The only difference between the good and the bad node is that the bad node first starts an AKKA Cluster object for a short amount of time, does some stuff with it, then tears it down.
system.terminate()
Await.result(system.whenTerminated, timeout)
...and then it starts it again. The cluster can then be formed between the two nodes. Both nodes report that they are in a cluster and can gossip properly.
The bad node can not perform HTTP requests even from the first one. HTTP entities are properly subscribed using response.entity.toStrict(20 seconds) or response.discardEntityBytes().
Hints or ideas on how to fix/debug the problem further are much appreciated!
More details:
I start one AKKA cluster on Kubernetes using the following dependencies:
"com.typesafe.akka" %% "akka-actor" % "2.6.16",
"com.typesafe.akka" %% "akka-slf4j" % "2.6.16",
"com.typesafe.akka" %% "akka-stream" % "2.6.16",
"com.typesafe.akka" %% "akka-cluster" % "2.6.16",
"com.typesafe.akka" %% "akka-cluster-tools" % "2.6.16",
"com.typesafe.akka" %% "akka-cluster-metrics" % "2.6.16",
"com.typesafe.akka" %% "akka-stream-typed" % "2.6.16",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.16",
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.16",
"com.typesafe.akka" %% "akka-cluster-sharding-typed" % "2.6.16",
"com.typesafe.akka" %% "akka-stream-typed" % "2.6.16",
"com.typesafe.akka" %% "akka-discovery" % "2.6.16",
"com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % "1.1.1",
"com.lightbend.akka.discovery" %% "akka-discovery-kubernetes-api" % "1.1.1",
"com.typesafe.akka" %% "akka-stream-testkit" % "2.6.16",
"com.typesafe.akka" %% "akka-stream-testkit" % "2.6.16" % "test",
"com.typesafe.akka" %% "akka-http" % "10.2.6",
"com.typesafe.akka" %% "akka-http-core" % "10.2.6",
"com.typesafe.akka" %% "akka-http-testkit" % "10.2.6",
"com.typesafe.akka" %% "akka-http-spray-json" % "10.2.6",
"com.typesafe.akka" %% "akka-http-testkit" % "10.2.6" % "test",
Below is the default configuration.
akka {
test {
timefactor = 3.0
}
loglevel = "DEBUG"
stdout-loglevel = "DEBUG"
loggers = ["akka.event.slf4j.Slf4jLogger"]
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
log-config-on-start = on
http {
parsing {
uri-parsing-mode = strict
error-logging-verbosity = full
}
server {
server-header = ""
verbose-error-messages = on
idle-timeout = 60s
max-connections = 2048
request-timeout = 30s
websocket.periodic-keep-alive-mode = pong
}
host-connection-pool {
max-connections = 32
max-open-requests = 64
max-connection-lifetime = 10 min
keep-alive-timeout = 10 min
response-entity-subscription-timeout = 2.second
}
routing {
verbose-error-messages = on
}
}
discovery {
method = config
}
management {
cluster.bootstrap {
contact-point-discovery {
service-name = "local-cluster"
discovery-method = config
required-contact-point-nr = 1
}
}
}
actor {
allow-java-serialization = on
warn-about-java-serializer-usage = off
provider = "cluster"
blocking-dispatcher {
type = "Dispatcher"
executor = "fork-join-executor"
fork-join-executor {
parallelism-min = 32
parallelism-max = 128
}
throughput = 1
}
}
cluster {
min-nr-of-members = 1
log-info-verbose = off
sharding {
passivate-idle-entity-after = off
}
}
remote {
log-remote-lifecycle-events = off
artery {
transport = tcp
canonical.port = 0
bind.port = 0
bind.bind-timeout = 10s
}
}
extensions = [
"akka.cluster.metrics.ClusterMetricsExtension",
"akka.cluster.pubsub.DistributedPubSub"
]
}
On Kubernetes, the configuration is overwritten with the following:
akka {
discovery {
kubernetes-api {
pod-label-selector = "app=my-app"
pod-domain = "cluster.local"
}
}
management {
cluster.bootstrap {
contact-point-discovery {
discovery-method = kubernetes-api
service-name = ${?AKKA_CLUSTER_BOOTSTRAP_SERVICE_NAME}
service-namespace = "staging.svc.cluster.local"
interval = 10 seconds
required-contact-point-nr = 0
}
}
}
cluster {
configuration-compatibility-check.enforce-on-join = off
shutdown-after-unsuccessful-join-seed-nodes = 120s
downing-provider-class = "akka.cluster.sbr.SplitBrainResolverProvider"
sharding {
use-lease = "akka.coordination.lease.kubernetes"
}
split-brain-resolver {
active-strategy = "lease-majority"
lease-majority {
lease-implementation = "akka.coordination.lease.kubernetes"
}
}
}
}

Related

swagger-akka-http 2.x Set List of Objects in the response

I have a problem. I want to send a list of objects in the response of an API using Swagger. type = "array" does not work for me.
I saw a topic Set List of Objects in Swagger API response , but it is an old version of lib. Annotation has changed. ApiResponse used to have responseContainer param, but now it is gone.
I have akka-http server.
val akkaVersion = "2.5.17"
val akkaHttpVersion = "10.1.5"
libraryDependencies ++= Seq(
"javax.ws.rs" % "javax.ws.rs-api" % "2.0.1",
"com.github.swagger-akka-http" %% "swagger-akka-http" % "2.0.0",
"com.github.swagger-akka-http" %% "swagger-scala-module" % "2.0.2",
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"ch.megard" %% "akka-http-cors" % "0.3.0",
"org.slf4j" % "slf4j-simple" % "1.7.25"
)
I create get route and describe it with swagger annotations.
#GET
#Path("offer-statuses/all")
#Produces(Array("application/json"))
#Operation(
tags = Array("offers"),
summary = "update periods",
responses = Array(
new ApiResponse(
responseCode = "200",
description = "OfferName response",
content = Array(
new Content(schema = new Schema(`type` = "array", implementation = classOf[EnumRow])))
),
new ApiResponse(responseCode = "400",
description = "Bad Request",
content = Array(new Content(schema = new Schema(implementation = classOf[BadRequest])))),
new ApiResponse(responseCode = "403",
description = "Forbidden",
content = Array(new Content(schema = new Schema(implementation = classOf[String]))))
)
)
def allOfferStatuses: Route = {
path("offers" / "offer-statuses" / "all") {
get {
applicationEnumsService.listAllOfferStatuses()
}
}
}
def listAllOfferStatuses(): List[EnumRow]
case class EnumRow(id: Int, name: String)
and it builds json:
"/api/v1/offers/offer-statuses/all" : {
"get" : {
"tags" : [ "offers" ],
"summary" : "update periods",
"operationId" : "allOfferStatuses",
"responses" : {
"200" : {
"description" : "OfferName response",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/EnumRow"
}
}
}
},
"400" : {
"description" : "Bad Request",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/BadRequest"
}
}
}
},
"403" : {
"description" : "Forbidden",
"content" : {
"application/json" : {
"schema" : {
"type" : "string"
}
}
}
}
}
}
}
},
"EnumRow" : {
"required" : [ "id", "name" ],
"type" : "object",
"properties" : {
"id" : {
"type" : "integer",
"format" : "int32"
},
"name" : {
"type" : "string"
}
}
},
I found a solution:
new ArraySchema(schema = new Schema(implementation = classOf[EnumRow]))
This seems to work for me for a single file upload from swagger:
import java.io.File
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.FileInfo
import com.demo.erp.routes.SwaggerUi.{complete, path, post, storeUploadedFile}
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.{Content, Schema}
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
import javax.ws.rs.core.MediaType
import javax.ws.rs.{Consumes, POST, Path}
#Tag(name = "File Upload")
class FileUploadRouter {
//just for swagger
case class FileUpload(#Schema(`type` = "string", format = "binary", description = "file") file: File)
def tempDestination(fileInfo: FileInfo): File = File.createTempFile(fileInfo.fileName, ".tmp")
val routes: Route = fileUpload
#POST
#Consumes(Array(MediaType.MULTIPART_FORM_DATA))
#Path("upload")
#Operation(
summary = "File upload",
description = "Upload file",
requestBody = new RequestBody(
description = "File",
content = Array(
new Content(
mediaType = MediaType.MULTIPART_FORM_DATA,
schema = new Schema(implementation = classOf[FileUpload])
)
)
),
responses = Array(
new ApiResponse(
responseCode = "200",
description = "File uploaded",
),
new ApiResponse(responseCode = "400", description = "Bad request"),
new ApiResponse(responseCode = "500", description = "Internal server error")
)
)
def fileUpload: Route = post {
path("upload") {
storeUploadedFile("file", tempDestination) {
case (metadata, file) =>
// do something with the file and file metadata ...
println("Metadata: " + metadata)
println("File: " + file)
complete(StatusCodes.OK, "Uploaded")
}
}
}
}
With dependencies:
lazy val akkaHttpVersion = "10.2.1"
lazy val akkaVersion = "2.6.10"
lazy val swaggerVersion = "2.1.5"
lazy val jacksonVersion = "2.11.3"
lazy val root = (project in file("."))
.enablePlugins(JavaAppPackaging)
.settings(
inThisBuild(List(organization := "com.demo.erp", scalaVersion := "2.13.4")),
name := "demo-erp",
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"com.typesafe.play" %% "play-json" % "2.9.1",
"org.mongodb.scala" %% "mongo-scala-driver" % "4.1.1",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"ch.rasc" % "bsoncodec" % "1.0.1",
"com.github.pjfanning" %% "scala-faker" % "0.5.0",
"javax.ws.rs" % "javax.ws.rs-api" % "2.1.1",
"com.github.swagger-akka-http" %% "swagger-akka-http" % "2.2.0",
"com.github.swagger-akka-http" %% "swagger-scala-module" % "2.1.3",
"com.github.swagger-akka-http" %% "swagger-enumeratum-module" % "2.0.0",
"com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion,
"pl.iterators" %% "kebs-spray-json" % "1.8.1",
"io.swagger.core.v3" % "swagger-core" % swaggerVersion,
"io.swagger.core.v3" % "swagger-annotations" % swaggerVersion,
"io.swagger.core.v3" % "swagger-models" % swaggerVersion,
"io.swagger.core.v3" % "swagger-jaxrs2" % swaggerVersion,
"ch.megard" %% "akka-http-cors" % "1.1.0",
"com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
"com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test,
"org.scalatest" %% "scalatest" % "3.2.3" % Test
)
)

Slick/HikariCP/MySQL Always Timing Out

I am using Play (2.6) with Slick, my database connection times out every second or third try. The only way to get the connection up again is by restarting the app with sbt run. It's driving me crazy, any help appreciated.
To be clear, I'm using Slick with a local MySQL database for very lightweight usage.
build.sbt
libraryDependencies += "mysql" % "mysql-connector-java" % "8.0.11"
libraryDependencies += "com.typesafe.slick" %% "slick" % "3.2.3"
libraryDependencies += "com.typesafe.slick" %% "slick-hikaricp" % "3.2.3"
application.conf
# db connections = ((physical_core_count * 2) + effective_spindle_count)
fixedConnectionPool = 5
repository.dispatcher {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = ${fixedConnectionPool}
}
}
Error message:
java.sql.SQLTransientConnectionException: <dbconfig> - Connection is not available, request timed out after 1001ms.
Stack Trace:
com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:548)
com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:186)
com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:145)
com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:83)
slick.jdbc.hikaricp.HikariCPJdbcDataSource.createConnection(HikariCPJdbcDataSource.scala:14)
slick.jdbc.JdbcBackend$BaseSession.<init>(JdbcBackend.scala:453)
slick.jdbc.JdbcBackend$DatabaseDef.createSession(JdbcBackend.scala:46)
slick.jdbc.JdbcBackend$DatabaseDef.createSession(JdbcBackend.scala:37)
slick.basic.BasicBackend$DatabaseDef.acquireSession(BasicBackend.scala:249)
slick.basic.BasicBackend$DatabaseDef.acquireSession$(BasicBackend.scala:248)
slick.jdbc.JdbcBackend$DatabaseDef.acquireSession(JdbcBackend.scala:37)
slick.basic.BasicBackend$DatabaseDef$$anon$2.run(BasicBackend.scala:274)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
java.lang.Thread.run(Thread.java:748)
Update
I didn't include the specific db config in application.conf:
<name> {
slick.driver = scala.slick.driver.MySQLDriver
driver = "com.mysql.cj.jdbc.Driver"
url = __
user = __
password = __
}
I've added to the config:
keepAliveConnection = true
connectionPool = disabled
And it's working fine.

Akka Streams Hikari Connection Pool for MySQL Streaming

I am streaming data from mysql using Slick 3 and Akka Streams.
This is how I build my source
import slick.jdbc.MySQLProfile.api._
val enableJdbcStreaming: (java.sql.Statement) => Unit = {statement =>
if (statement.isWrapperFor(classOf[com.mysql.cj.jdbc.StatementImpl])) {
statement.unwrap(classOf[com.mysql.cj.jdbc.StatementImpl]).enableStreamingResults()
}
}
val query = Tables.Foo.filter(r => r.isActive === true)
.map(r => r.id).result.withStatementParameters(statementInit = enableJdbcStreaming)
Source.fromPublisher(db.stream(query))
My application runs for like 20 minutes and then shuts down with the following error
[error] Exception in thread "abhipool network timeout executor" java.lang.NullPointerException
[info] 15:31:46 INFO [HikariPool] - abhipool - Close initiated...
[error] at com.mysql.cj.mysqla.io.MysqlaProtocol.setSocketTimeout(MysqlaProtocol.java:1397)
[error] at com.mysql.cj.mysqla.MysqlaSession$1.run(MysqlaSession.java:401)
[error] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
[error] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
[error] at java.lang.Thread.run(Thread.java:745)
I have a feeling that because my query is running for a very long time there is some kind of timeout occurring which is initiating this shutdown.
My connection
mysql {
profile = "slick.jdbc.MySQLProfile$"
dataSourceClass = "slick.jdbc.DatabaseUrlDataSource"
properties {
driver = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://foo:3306/bar?useLegacyDatetimeCode=false&serverTimezone=America/Chicago"
user = "foo"
password = "bar"
}
connectionTimeout = 0
idleTimeout = 0
maxLifetime = 0
maxConnections = 40
minConnections = 10
poolName = "abhipool"
numThreads = 10
}
Dependencies
"com.typesafe.slick" %% "slick" % "3.2.1",
"com.typesafe.slick" %% "slick-hikaricp" % "3.2.1",
"mysql" % "mysql-connector-java" % "6.0.6",
How can I configure my application database connections so that even if my streaming application streams data for several days... it keeps running.
There is an extremely lengthy conversation about this same issue here but it doesn't tell me how to really fix this issue. This issues makes it totally impossible to write long running streaming tasks which use Mysql as a source.
You can configure the MySQL driver by adding parameters in the URL
url = "jdbc:mysql://foo:3306/bar?useLegacyDatetimeCode=false&serverTimezone=America/Chicago&socketTimeout=30000"
I put 30000 for the sake of the example, put the right value that fits your need

ElasticSearch import issue

I'm trying to create a client for ES in SCALA [ school project ] .
but when I want to import Elastic search I got some problems
I've written a sbt file :
libraryDependencies += "org.elasticsearch" %% "elasticsearch" % "1.4.2"
libraryDependencies += "org.apache.lucene" % "lucene-core" % "4.10.2"
with other lucene
and when I try to use it :
import org.elasticsearch.node.Nodebuilder.*
object Setup {
Node node = nodeBuilder().node();
Client client = node.client();
}
it does recognize org.elasticsearch.node. but not .Nodebuilder.
Anyone has an idea ?
solved
import org.elasticsearch.node.NodeBuilder.nodeBuilder
val node = nodeBuilder().node()
val client = node.client()
I would suggest you to use the following library: https://github.com/sksamuel/elastic4s

Akka persistence with confirmed delivery gives inconsistent results

I have been playing around with Akka Persistence and have written the following program to test my understanding. The problem is that I get different results each time I run this program. The correct answer is 49995000 but I don't always get that. I have cleaned out the journal directory between each run but it does not make any difference. Can anyone see what's going wrong? The program simply sums all the numbers from 1 to n (where n is 9999 in the code below).
The correct answer is : (n * (n+1)) / 2. For n=9999 that's 49995000.
EDIT: Seems to work more consistently with JDK 8 than with JDK 7. Should I be using JDK 8 only?
package io.github.ourkid.akka.aggregator.guaranteed
import akka.actor.Actor
import akka.actor.ActorPath
import akka.actor.ActorSystem
import akka.actor.Props
import akka.actor.actorRef2Scala
import akka.persistence.AtLeastOnceDelivery
import akka.persistence.PersistentActor
case class ExternalRequest(updateAmount : Int)
case class CountCommand(deliveryId : Long, updateAmount : Int)
case class Confirm(deliveryId : Long)
sealed trait Evt
case class CountEvent(updateAmount : Int) extends Evt
case class ConfirmEvent(deliveryId : Long) extends Evt
class TestGuaranteedDeliveryActor(counter : ActorPath) extends PersistentActor with AtLeastOnceDelivery {
override def persistenceId = "persistent-actor-ref-1"
override def receiveCommand : Receive = {
case ExternalRequest(updateAmount) => persist(CountEvent(updateAmount))(updateState)
case Confirm(deliveryId) => persist(ConfirmEvent(deliveryId)) (updateState)
}
override def receiveRecover : Receive = {
case evt : Evt => updateState(evt)
}
def updateState(evt:Evt) = evt match {
case CountEvent(updateAmount) => deliver(counter, id => CountCommand(id, updateAmount))
case ConfirmEvent(deliveryId) => confirmDelivery(deliveryId)
}
}
class FactorialActor extends Actor {
var count = 0
def receive = {
case CountCommand(deliveryId : Long, updateAmount:Int) => {
count = count + updateAmount
sender() ! Confirm(deliveryId)
}
case "print" => println(count)
}
}
object GuaranteedDeliveryTest extends App {
val system = ActorSystem()
val factorial = system.actorOf(Props[FactorialActor])
val delActor = system.actorOf(Props(classOf[TestGuaranteedDeliveryActor], factorial.path))
import system.dispatcher
system.scheduler.schedule(0 seconds, 2 seconds) { factorial ! "print" }
for (i <- 1 to 9999)
delActor ! ExternalRequest(i)
}
SBT file
name := "akka_aggregator"
organization := "io.github.ourkid"
version := "0.0.1-SNAPSHOT"
scalaVersion := "2.11.4"
scalacOptions ++= Seq("-unchecked", "-deprecation")
resolvers ++= Seq(
"Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
)
val Akka = "2.3.7"
val Spray = "1.3.2"
libraryDependencies ++= Seq(
// Core Akka
"com.typesafe.akka" %% "akka-actor" % Akka,
"com.typesafe.akka" %% "akka-cluster" % Akka,
"com.typesafe.akka" %% "akka-persistence-experimental" % Akka,
"org.iq80.leveldb" % "leveldb" % "0.7",
"org.fusesource.leveldbjni" % "leveldbjni-all" % "1.8",
// For future REST API
"io.spray" %% "spray-httpx" % Spray,
"io.spray" %% "spray-can" % Spray,
"io.spray" %% "spray-routing" % Spray,
"org.typelevel" %% "scodec-core" % "1.3.0",
// CSV reader
"net.sf.opencsv" % "opencsv" % "2.3",
// Logging
"com.typesafe.akka" %% "akka-slf4j" % Akka,
"ch.qos.logback" % "logback-classic" % "1.0.13",
// Testing
"org.scalatest" %% "scalatest" % "2.2.1" % "test",
"com.typesafe.akka" %% "akka-testkit" % Akka % "test",
"io.spray" %% "spray-testkit" % Spray % "test",
"org.scalacheck" %% "scalacheck" % "1.11.6" % "test"
)
fork := true
mainClass in assembly := Some("io.github.ourkid.akka.aggregator.TestGuaranteedDeliveryActor")
application.conf file
##########################################
# Akka Persistence Reference Config File #
##########################################
akka {
# Loggers to register at boot time (akka.event.Logging$DefaultLogger logs
# to STDOUT)
loggers = ["akka.event.slf4j.Slf4jLogger"]
# Log level used by the configured loggers (see "loggers") as soon
# as they have been started; before that, see "stdout-loglevel"
# Options: OFF, ERROR, WARNING, INFO, DEBUG
loglevel = "DEBUG"
# Log level for the very basic logger activated during ActorSystem startup.
# This logger prints the log messages to stdout (System.out).
# Options: OFF, ERROR, WARNING, INFO, DEBUG
stdout-loglevel = "INFO"
# Filter of log events that is used by the LoggingAdapter before
# publishing log events to the eventStream.
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
# Protobuf serialization for persistent messages
actor {
serializers {
akka-persistence-snapshot = "akka.persistence.serialization.SnapshotSerializer"
akka-persistence-message = "akka.persistence.serialization.MessageSerializer"
}
serialization-bindings {
"akka.persistence.serialization.Snapshot" = akka-persistence-snapshot
"akka.persistence.serialization.Message" = akka-persistence-message
}
}
persistence {
journal {
# Maximum size of a persistent message batch written to the journal.
max-message-batch-size = 200
# Maximum size of a deletion batch written to the journal.
max-deletion-batch-size = 10000
# Path to the journal plugin to be used
plugin = "akka.persistence.journal.leveldb"
# In-memory journal plugin.
inmem {
# Class name of the plugin.
class = "akka.persistence.journal.inmem.InmemJournal"
# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.actor.default-dispatcher"
}
# LevelDB journal plugin.
leveldb {
# Class name of the plugin.
class = "akka.persistence.journal.leveldb.LeveldbJournal"
# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher"
# Dispatcher for message replay.
replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher"
# Storage location of LevelDB files.
dir = "journal"
# Use fsync on write
fsync = on
# Verify checksum on read.
checksum = off
# Native LevelDB (via JNI) or LevelDB Java port
native = on
# native = off
}
# Shared LevelDB journal plugin (for testing only).
leveldb-shared {
# Class name of the plugin.
class = "akka.persistence.journal.leveldb.SharedLeveldbJournal"
# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.actor.default-dispatcher"
# timeout for async journal operations
timeout = 10s
store {
# Dispatcher for shared store actor.
store-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher"
# Dispatcher for message replay.
replay-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher"
# Storage location of LevelDB files.
dir = "journal"
# Use fsync on write
fsync = on
# Verify checksum on read.
checksum = off
# Native LevelDB (via JNI) or LevelDB Java port
native = on
}
}
}
snapshot-store {
# Path to the snapshot store plugin to be used
plugin = "akka.persistence.snapshot-store.local"
# Local filesystem snapshot store plugin.
local {
# Class name of the plugin.
class = "akka.persistence.snapshot.local.LocalSnapshotStore"
# Dispatcher for the plugin actor.
plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher"
# Dispatcher for streaming snapshot IO.
stream-dispatcher = "akka.persistence.dispatchers.default-stream-dispatcher"
# Storage location of snapshot files.
dir = "snapshots"
}
}
view {
# Automated incremental view update.
auto-update = on
# Interval between incremental updates
auto-update-interval = 5s
# Maximum number of messages to replay per incremental view update. Set to
# -1 for no upper limit.
auto-update-replay-max = -1
}
at-least-once-delivery {
# Interval between redelivery attempts
redeliver-interval = 5s
# Maximum number of unconfirmed messages that will be sent in one redelivery burst
redelivery-burst-limit = 10000
# After this number of delivery attempts a `ReliableRedelivery.UnconfirmedWarning`
# message will be sent to the actor.
warn-after-number-of-unconfirmed-attempts = 5
# Maximum number of unconfirmed messages that an actor with AtLeastOnceDelivery is
# allowed to hold in memory.
max-unconfirmed-messages = 100000
}
dispatchers {
default-plugin-dispatcher {
type = PinnedDispatcher
executor = "thread-pool-executor"
}
default-replay-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
parallelism-min = 2
parallelism-max = 8
}
}
default-stream-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
parallelism-min = 2
parallelism-max = 8
}
}
}
}
}
Correct output:
18:02:36.684 [default-akka.actor.default-dispatcher-3] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
18:02:36.684 [default-akka.actor.default-dispatcher-3] DEBUG akka.event.EventStream - logger log1-Slf4jLogger started
18:02:36.684 [default-akka.actor.default-dispatcher-3] DEBUG akka.event.EventStream - Default Loggers started
0
18:02:36.951 [default-akka.actor.default-dispatcher-14] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.persistence.serialization.MessageSerializer] for message [akka.persistence.PersistentImpl]
18:02:36.966 [default-akka.actor.default-dispatcher-3] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.serialization.JavaSerializer] for message [io.github.ourkid.akka.aggregator.guaranteed.CountEvent]
3974790
24064453
18:02:42.313 [default-akka.actor.default-dispatcher-11] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.serialization.JavaSerializer] for message [io.github.ourkid.akka.aggregator.guaranteed.ConfirmEvent]
49995000
49995000
49995000
49995000
Incorrect run:
17:56:22.493 [default-akka.actor.default-dispatcher-4] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
17:56:22.508 [default-akka.actor.default-dispatcher-4] DEBUG akka.event.EventStream - logger log1-Slf4jLogger started
17:56:22.508 [default-akka.actor.default-dispatcher-4] DEBUG akka.event.EventStream - Default Loggers started
0
17:56:22.750 [default-akka.actor.default-dispatcher-2] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.persistence.serialization.MessageSerializer] for message [akka.persistence.PersistentImpl]
17:56:22.765 [default-akka.actor.default-dispatcher-7] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.serialization.JavaSerializer] for message [io.github.ourkid.akka.aggregator.guaranteed.CountEvent]
3727815
22167811
17:56:28.391 [default-akka.actor.default-dispatcher-3] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.serialization.JavaSerializer] for message [io.github.ourkid.akka.aggregator.guaranteed.ConfirmEvent]
49995000
51084018
51084018
52316760
52316760
52316760
52316760
52316760
Another incorrect run:
17:59:12.122 [default-akka.actor.default-dispatcher-3] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
17:59:12.137 [default-akka.actor.default-dispatcher-3] DEBUG akka.event.EventStream - logger log1-Slf4jLogger started
17:59:12.137 [default-akka.actor.default-dispatcher-3] DEBUG akka.event.EventStream - Default Loggers started
0
17:59:12.387 [default-akka.actor.default-dispatcher-7] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.persistence.serialization.MessageSerializer] for message [akka.persistence.PersistentImpl]
17:59:12.402 [default-akka.actor.default-dispatcher-13] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.serialization.JavaSerializer] for message [io.github.ourkid.akka.aggregator.guaranteed.CountEvent]
2982903
17710176
49347145
17:59:18.204 [default-akka.actor.default-dispatcher-13] DEBUG a.s.Serialization(akka://default) - Using serializer[akka.serialization.JavaSerializer] for message [io.github.ourkid.akka.aggregator.guaranteed.ConfirmEvent]
51704199
51704199
55107844
55107844
55107844
55107844
You're using AtLeastOnceDelivery semantics. As it said here:
Note At-least-once delivery implies that original message send order
is not always preserved and the destination may receive duplicate
messages. That means that the semantics do not match those of a normal
ActorRef send operation:
it is not at-most-once delivery message order for the same
sender–receiver pair is not preserved due to possible resends after a
crash and restart of the destination messages are still delivered—to
the new actor incarnation These semantics is similar to what an
ActorPath represents (see Actor Lifecycle), therefore you need to
supply a path and not a reference when delivering messages. The
messages are sent to the path with an actor selection.
So some numbers may be received more than once. You can just ignore duplicate numbers inside FactorialActor or don't use this semantic.