diff --git a/frameworks/Java/play2-java/play2-java-ebean-hikaricp/app/controllers/Application.java b/frameworks/Java/play2-java/play2-java-ebean-hikaricp/app/controllers/Application.java index 48012ca6852..d543b960b8f 100644 --- a/frameworks/Java/play2-java/play2-java-ebean-hikaricp/app/controllers/Application.java +++ b/frameworks/Java/play2-java/play2-java-ebean-hikaricp/app/controllers/Application.java @@ -4,56 +4,41 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ThreadLocalRandom; -import javax.inject.Inject; - import models.Fortune; import models.World; import play.libs.Json; import play.mvc.Controller; import play.mvc.Result; -import utils.DatabaseExecutionContext; public class Application extends Controller { - private final DatabaseExecutionContext dbEc; - - @Inject - public Application(final DatabaseExecutionContext dbEc) { - this.dbEc = dbEc; - } - - public CompletionStage db() { - return getRandomWorlds(1).thenApply(worlds -> ok(Json.toJson(worlds.get(0)))); + public Result db() { + return ok(Json.toJson(getRandomWorlds(1).get(0))); } - public CompletionStage queries(final String queries) { - return getRandomWorlds(queryCount(queries)).thenApply(worlds -> ok(Json.toJson(worlds))); + public Result queries(final String queries) { + return ok(Json.toJson(getRandomWorlds(queryCount(queries)))); } - public CompletionStage fortunes() { - return CompletableFuture.supplyAsync(() -> { - final List fortunes = Fortune.findAll(); - fortunes.add(new Fortune("Additional fortune added at request time.")); - Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message)); + public Result fortunes() { + final List fortunes = Fortune.findAll(); + fortunes.add(new Fortune("Additional fortune added at request time.")); + Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message)); - return ok(views.html.fortunes.render(fortunes)); - }, dbEc); + return ok(views.html.fortunes.render(fortunes)); } - public CompletionStage update(final String queries) { - return getRandomWorlds(queryCount(queries)).thenApplyAsync(worlds -> { - final Random random = ThreadLocalRandom.current(); - for (final World world : worlds) { - world.randomNumber = (long) (random.nextInt(10000) + 1); - } + public Result update(final String queries) { + final List worlds = getRandomWorlds(queryCount(queries)); + final Random random = ThreadLocalRandom.current(); + for (final World world : worlds) { + world.randomNumber = (long) (random.nextInt(10000) + 1); + } - final List updatedWorlds = World.save(worlds); - return ok(Json.toJson(updatedWorlds)); - }, dbEc); + final List updatedWorlds = World.save(worlds); + return ok(Json.toJson(updatedWorlds)); } private int queryCount(final String queryCountString) { @@ -72,17 +57,15 @@ private int queryCount(final String queryCountString) { return queryCount; } - private CompletionStage> getRandomWorlds(final int n) { - return CompletableFuture.supplyAsync(() -> { - final Random random = ThreadLocalRandom.current(); - final List worlds = new ArrayList<>(n); - for (int i = 0; i < n; ++i) { - long randomId = random.nextInt(10000) + 1; - final World world = World.find(randomId); - worlds.add(world); - } - return worlds; - }, dbEc); + private List getRandomWorlds(final int n) { + final Random random = ThreadLocalRandom.current(); + final List worlds = new ArrayList<>(n); + for (int i = 0; i < n; ++i) { + long randomId = random.nextInt(10000) + 1; + final World world = World.find(randomId); + worlds.add(world); + } + return worlds; } } diff --git a/frameworks/Java/play2-java/play2-java-ebean-hikaricp/conf/application.conf b/frameworks/Java/play2-java/play2-java-ebean-hikaricp/conf/application.conf index caec66fec29..441fb488073 100644 --- a/frameworks/Java/play2-java/play2-java-ebean-hikaricp/conf/application.conf +++ b/frameworks/Java/play2-java/play2-java-ebean-hikaricp/conf/application.conf @@ -50,6 +50,8 @@ play.server { akka { actor { default-dispatcher { + executor = "thread-pool-executor" + fork-join-executor { # one thread per core is enough # https://github.com/playframework/playframework/issues/7242#issuecomment-295215448 @@ -63,11 +65,16 @@ akka { task-peeking-mode="LIFO" # based on https://www.playframework.com/documentation/2.7.x/Migration24#Thread-pool-configuration } + # https://www.playframework.com/documentation/2.7.x/ThreadPools#Highly-synchronous + thread-pool-executor { + fixed-pool-size = 44 # db conn pool (29) + number of cores (14) + housekeeping (1) + } + # https://doc.akka.io/docs/akka/2.5.11/dispatchers.html#looking-up-a-dispatcher # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. # Set to 1 for as fair as possible. - throughput = 64 + throughput = 1 } } } diff --git a/frameworks/Java/play2-java/play2-java-jooq-hikaricp/app/controllers/Application.java b/frameworks/Java/play2-java/play2-java-jooq-hikaricp/app/controllers/Application.java index eeedde3cd6e..ddf732d0c88 100644 --- a/frameworks/Java/play2-java/play2-java-jooq-hikaricp/app/controllers/Application.java +++ b/frameworks/Java/play2-java/play2-java-jooq-hikaricp/app/controllers/Application.java @@ -7,8 +7,6 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ThreadLocalRandom; import javax.inject.Inject; @@ -25,7 +23,6 @@ import play.db.Database; import play.mvc.Controller; import play.mvc.Result; -import utils.DatabaseExecutionContext; public class Application extends Controller { @@ -33,53 +30,49 @@ public class Application extends Controller { private static final JSONFormat JSON_FORMAT = JSONFormat.DEFAULT_FOR_RECORDS.recordFormat(RecordFormat.OBJECT); private final Database db; - private final DatabaseExecutionContext dbEc; @Inject - public Application(final Database db, final DatabaseExecutionContext dbEc) { + public Application(final Database db) { this.db = db; - this.dbEc = dbEc; } - public CompletionStage db() { - return getRandomWorlds(1).thenApply(worlds -> ok(worlds.get(0).formatJSON(JSON_FORMAT)).as(JSON)); + public Result db() { + return ok(getRandomWorlds(1).get(0).formatJSON(JSON_FORMAT)).as(JSON); } - public CompletionStage queries(final String queries) { - return getRandomWorlds(queryCount(queries)).thenApply(worlds -> ok(worlds.formatJSON(JSON_FORMAT)).as(JSON)); + public Result queries(final String queries) { + return ok(getRandomWorlds(queryCount(queries)).formatJSON(JSON_FORMAT)).as(JSON); } - public CompletionStage fortunes() { - return CompletableFuture.supplyAsync(() -> { - final List fortunes = this.db.withConnection(connection -> { - return DSL.using(connection, DIALECT).select(FORTUNE.ID, FORTUNE.MESSAGE).from(FORTUNE).fetchInto(FortuneRecord.class); - }); - fortunes.add(new FortuneRecord(UInteger.valueOf(0), "Additional fortune added at request time.")); - Collections.sort(fortunes, (f1, f2) -> f1.getMessage().compareTo(f2.getMessage())); + public Result fortunes() { + final List fortunes = this.db.withConnection(connection -> { + return DSL.using(connection, DIALECT).select(FORTUNE.ID, FORTUNE.MESSAGE).from(FORTUNE).fetchInto(FortuneRecord.class); + }); + fortunes.add(new FortuneRecord(UInteger.valueOf(0), "Additional fortune added at request time.")); + Collections.sort(fortunes, (f1, f2) -> f1.getMessage().compareTo(f2.getMessage())); - return ok(views.html.fortunes.render(fortunes)); - }, dbEc); + return ok(views.html.fortunes.render(fortunes)); } - public CompletionStage update(final String queries) { - return getRandomWorlds(queryCount(queries)).thenApplyAsync(worlds -> { - final Random random = ThreadLocalRandom.current(); - for (final WorldRecord world : worlds) { - world.setRandomnumber((random.nextInt(10000) + 1)); - } + public Result update(final String queries) { + final org.jooq.Result worlds = getRandomWorlds(queryCount(queries)); - final int batchSize = 25; - final int batches = ((worlds.size() / batchSize) + 1); - this.db.withConnection(connection -> { - final DSLContext sql = DSL.using(connection, DIALECT); - for ( int i = 0 ; i < batches ; ++i ) { - sql.batchUpdate(worlds.subList(i * batchSize, Math.min((i + 1) * batchSize, worlds.size()))).execute(); - } - return null; - }); + final Random random = ThreadLocalRandom.current(); + for (final WorldRecord world : worlds) { + world.setRandomnumber((random.nextInt(10000) + 1)); + } - return ok(worlds.formatJSON(JSON_FORMAT)).as(JSON); - }, dbEc); + final int batchSize = 25; + final int batches = ((worlds.size() / batchSize) + 1); + this.db.withConnection(connection -> { + final DSLContext sql = DSL.using(connection, DIALECT); + for ( int i = 0 ; i < batches ; ++i ) { + sql.batchUpdate(worlds.subList(i * batchSize, Math.min((i + 1) * batchSize, worlds.size()))).execute(); + } + return null; + }); + + return ok(worlds.formatJSON(JSON_FORMAT)).as(JSON); } private int queryCount(final String queryCountString) { @@ -98,24 +91,22 @@ private int queryCount(final String queryCountString) { return queryCount; } - private CompletionStage> getRandomWorlds(final int n) { - return CompletableFuture.supplyAsync(() -> { - final Random random = ThreadLocalRandom.current(); - org.jooq.Result worlds = null; - for (int i = 0; i < n; ++i) { - long randomId = random.nextInt(10000) + 1; - final org.jooq.Result world = this.db.withConnection(connection -> { - return DSL.using(connection, DIALECT).selectFrom(WORLD).where(WORLD.ID.eq(UInteger.valueOf(randomId))).fetch(); - }); - - if(worlds == null) { - worlds = world; - } else { - worlds.add(world.get(0)); - } + private org.jooq.Result getRandomWorlds(final int n) { + final Random random = ThreadLocalRandom.current(); + org.jooq.Result worlds = null; + for (int i = 0; i < n; ++i) { + long randomId = random.nextInt(10000) + 1; + final org.jooq.Result world = this.db.withConnection(connection -> { + return DSL.using(connection, DIALECT).selectFrom(WORLD).where(WORLD.ID.eq(UInteger.valueOf(randomId))).fetch(); + }); + + if(worlds == null) { + worlds = world; + } else { + worlds.add(world.get(0)); } - return worlds; - }, dbEc); + } + return worlds; } } diff --git a/frameworks/Java/play2-java/play2-java-jooq-hikaricp/conf/application.conf b/frameworks/Java/play2-java/play2-java-jooq-hikaricp/conf/application.conf index 9a2de2bf27e..8d338099055 100644 --- a/frameworks/Java/play2-java/play2-java-jooq-hikaricp/conf/application.conf +++ b/frameworks/Java/play2-java/play2-java-jooq-hikaricp/conf/application.conf @@ -50,6 +50,8 @@ play.server { akka { actor { default-dispatcher { + executor = "thread-pool-executor" + fork-join-executor { # one thread per core is enough # https://github.com/playframework/playframework/issues/7242#issuecomment-295215448 @@ -63,11 +65,16 @@ akka { task-peeking-mode="LIFO" # based on https://www.playframework.com/documentation/2.7.x/Migration24#Thread-pool-configuration } + # https://www.playframework.com/documentation/2.7.x/ThreadPools#Highly-synchronous + thread-pool-executor { + fixed-pool-size = 44 # db conn pool (29) + number of cores (14) + housekeeping (1) + } + # https://doc.akka.io/docs/akka/2.5.11/dispatchers.html#looking-up-a-dispatcher # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. # Set to 1 for as fair as possible. - throughput = 64 + throughput = 1 } } } diff --git a/frameworks/Java/play2-java/play2-java-jpa-hikaricp/app/controllers/Application.java b/frameworks/Java/play2-java/play2-java-jpa-hikaricp/app/controllers/Application.java index 3ee13e6c4e0..601d73b89fd 100644 --- a/frameworks/Java/play2-java/play2-java-jpa-hikaricp/app/controllers/Application.java +++ b/frameworks/Java/play2-java/play2-java-jpa-hikaricp/app/controllers/Application.java @@ -4,8 +4,6 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ThreadLocalRandom; import javax.inject.Inject; @@ -16,47 +14,41 @@ import play.libs.Json; import play.mvc.Controller; import play.mvc.Result; -import utils.DatabaseExecutionContext; public class Application extends Controller { private final JPAApi jpa; - private final DatabaseExecutionContext dbEc; @Inject - public Application(final JPAApi jpa, final DatabaseExecutionContext dbEc) { + public Application(final JPAApi jpa) { this.jpa = jpa; - this.dbEc = dbEc; } - public CompletionStage db() { - return getRandomWorlds(1).thenApply(worlds -> ok(Json.toJson(worlds.get(0)))); + public Result db() { + return ok(Json.toJson(getRandomWorlds(1).get(0))); } - public CompletionStage queries(final String queries) { - return getRandomWorlds(queryCount(queries)).thenApply(worlds -> ok(Json.toJson(worlds))); + public Result queries(final String queries) { + return ok(Json.toJson(getRandomWorlds(queryCount(queries)))); } - public CompletionStage fortunes() { - return CompletableFuture.supplyAsync(() -> { - final List fortunes = Fortune.findAll(this.jpa); - fortunes.add(new Fortune("Additional fortune added at request time.")); - Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message)); + public Result fortunes() { + final List fortunes = Fortune.findAll(this.jpa); + fortunes.add(new Fortune("Additional fortune added at request time.")); + Collections.sort(fortunes, (f1, f2) -> f1.message.compareTo(f2.message)); - return ok(views.html.fortunes.render(fortunes)); - }, dbEc); + return ok(views.html.fortunes.render(fortunes)); } - public CompletionStage update(final String queries) { - return getRandomWorlds(queryCount(queries)).thenApplyAsync(worlds -> { - final Random random = ThreadLocalRandom.current(); - for (final World world : worlds) { - world.randomNumber = (long) (random.nextInt(10000) + 1); - } + public Result update(final String queries) { + final List worlds = getRandomWorlds(queryCount(queries)); + final Random random = ThreadLocalRandom.current(); + for (final World world : worlds) { + world.randomNumber = (long) (random.nextInt(10000) + 1); + } - final List updatedWorlds = World.save(worlds, this.jpa); - return ok(Json.toJson(updatedWorlds)); - }, dbEc); + final List updatedWorlds = World.save(worlds, this.jpa); + return ok(Json.toJson(updatedWorlds)); } private int queryCount(final String queryCountString) { @@ -75,17 +67,15 @@ private int queryCount(final String queryCountString) { return queryCount; } - private CompletionStage> getRandomWorlds(final int n) { - return CompletableFuture.supplyAsync(() -> { - final Random random = ThreadLocalRandom.current(); - final List worlds = new ArrayList<>(n); - for (int i = 0; i < n; ++i) { - long randomId = random.nextInt(10000) + 1; - final World world = World.findById(randomId, this.jpa); - worlds.add(world); - } - return worlds; - }, dbEc); + private List getRandomWorlds(final int n) { + final Random random = ThreadLocalRandom.current(); + final List worlds = new ArrayList<>(n); + for (int i = 0; i < n; ++i) { + long randomId = random.nextInt(10000) + 1; + final World world = World.findById(randomId, this.jpa); + worlds.add(world); + } + return worlds; } } diff --git a/frameworks/Java/play2-java/play2-java-jpa-hikaricp/conf/application.conf b/frameworks/Java/play2-java/play2-java-jpa-hikaricp/conf/application.conf index ca391488bd4..38d94dc900c 100644 --- a/frameworks/Java/play2-java/play2-java-jpa-hikaricp/conf/application.conf +++ b/frameworks/Java/play2-java/play2-java-jpa-hikaricp/conf/application.conf @@ -50,6 +50,8 @@ play.server { akka { actor { default-dispatcher { + executor = "thread-pool-executor" + fork-join-executor { # one thread per core is enough # https://github.com/playframework/playframework/issues/7242#issuecomment-295215448 @@ -63,11 +65,16 @@ akka { task-peeking-mode="LIFO" # based on https://www.playframework.com/documentation/2.7.x/Migration24#Thread-pool-configuration } + # https://www.playframework.com/documentation/2.7.x/ThreadPools#Highly-synchronous + thread-pool-executor { + fixed-pool-size = 44 # db conn pool (29) + number of cores (14) + housekeeping (1) + } + # https://doc.akka.io/docs/akka/2.5.11/dispatchers.html#looking-up-a-dispatcher # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. # Set to 1 for as fair as possible. - throughput = 64 + throughput = 1 } } } diff --git a/frameworks/Scala/play2-scala/play2-scala-anorm/app/controllers/Application.scala b/frameworks/Scala/play2-scala/play2-scala-anorm/app/controllers/Application.scala index a8048d9d677..373f759a070 100644 --- a/frameworks/Scala/play2-scala/play2-scala-anorm/app/controllers/Application.scala +++ b/frameworks/Scala/play2-scala/play2-scala-anorm/app/controllers/Application.scala @@ -3,28 +3,26 @@ package controllers import javax.inject.{Inject, Singleton} import play.api.mvc._ -import play.mvc.Http import play.api.libs.json.Json import java.util.concurrent._ import models.{WorldDAO, FortunesDAO, World, Fortune} import utils.DbOperation -import scala.concurrent.{Future, ExecutionContext} @Singleton() -class Application @Inject() (fortunesDAO: FortunesDAO, worldDAO: WorldDAO, dbOperation: DbOperation, val controllerComponents: ControllerComponents)(implicit ec: ExecutionContext) +class Application @Inject() (fortunesDAO: FortunesDAO, worldDAO: WorldDAO, dbOperation: DbOperation, val controllerComponents: ControllerComponents) extends BaseController { - def getRandomWorlds(n: Int): Future[Seq[World]] = dbOperation.asyncDbOp { implicit connection => + def getRandomWorlds(n: Int): Seq[World] = dbOperation.syncDbOp { implicit connection => for (_ <- 1 to n) yield { worldDAO.findById(getNextRandom) } } - def getFortunes: Future[Seq[Fortune]] = dbOperation.asyncDbOp { implicit connection => + def getFortunes: Seq[Fortune] = dbOperation.syncDbOp { implicit connection => fortunesDAO.getAll } - def updateWorlds(n: Int): Future[Seq[World]] = dbOperation.asyncDbOp { implicit connection => + def updateWorlds(n: Int): Seq[World] = dbOperation.syncDbOp { implicit connection => for(_ <- 1 to n) yield { val world = worldDAO.findById(getNextRandom) val updatedWorld = world.copy(randomNumber = getNextRandom) @@ -44,31 +42,23 @@ class Application @Inject() (fortunesDAO: FortunesDAO, worldDAO: WorldDAO, dbOpe import models.WorldJsonHelpers.toJson - def db = Action.async { - getRandomWorlds(1).map { worlds => - Ok(Json.toJson(worlds.head)) - } + def db = Action { + Ok(Json.toJson(getRandomWorlds(1).head)) } - def queries(countString: String) = Action.async { + def queries(countString: String) = Action { val n = parseCount(countString) - getRandomWorlds(n).map { worlds => - Ok(Json.toJson(worlds)) - } + Ok(Json.toJson(getRandomWorlds(n))) } - def fortunes() = Action.async { - getFortunes.map { dbFortunes => - val appendedFortunes = Fortune(0, "Additional fortune added at request time.") :: dbFortunes.to[List] - Ok(views.html.fortune(appendedFortunes)) - } + def fortunes() = Action { + val appendedFortunes = Fortune(0, "Additional fortune added at request time.") :: getFortunes.to[List] + Ok(views.html.fortune(appendedFortunes)) } - def update(queries: String) = Action.async { + def update(queries: String) = Action { val n = parseCount(queries) - updateWorlds(n).map { worlds => - Ok(Json.toJson(worlds)) - } + Ok(Json.toJson(updateWorlds(n))) } private def parseCount(s: String): Int = { diff --git a/frameworks/Scala/play2-scala/play2-scala-anorm/app/utils/DbOperation.scala b/frameworks/Scala/play2-scala/play2-scala-anorm/app/utils/DbOperation.scala index 896031225f0..0d3ca45d17b 100644 --- a/frameworks/Scala/play2-scala/play2-scala-anorm/app/utils/DbOperation.scala +++ b/frameworks/Scala/play2-scala/play2-scala-anorm/app/utils/DbOperation.scala @@ -12,20 +12,18 @@ import scala.concurrent.Future @Singleton class DbOperation @Inject() (protected val db: Database, - configuration: Configuration, dbEc: DatabaseExecutionContext) { + configuration: Configuration) { /** * Run a DB operation in the DB context. */ - def asyncDbOp[T](op: Connection => T): Future[T] = { + def syncDbOp[T](op: Connection => T): T = { // If the thread-pool queue used by the database grows too large then our server // is probably struggling, and we should start dropping requests. If we don't // then we'll just slow everything down and it will fail anyway. Better to fail // quickly rather than slowly. Set the max size of our queue something above the // number of concurrent connections that we expect to be handling. - Future { - db.withConnection { connection => op(connection) } - }(dbEc) + db.withConnection { connection => op(connection) } } } diff --git a/frameworks/Scala/play2-scala/play2-scala-anorm/conf/application.conf b/frameworks/Scala/play2-scala/play2-scala-anorm/conf/application.conf index 4b5924e3197..39be8705643 100755 --- a/frameworks/Scala/play2-scala/play2-scala-anorm/conf/application.conf +++ b/frameworks/Scala/play2-scala/play2-scala-anorm/conf/application.conf @@ -45,6 +45,8 @@ play.server { akka { actor { default-dispatcher { + executor = "thread-pool-executor" + fork-join-executor { # one thread per core is enough # https://github.com/playframework/playframework/issues/7242#issuecomment-295215448 @@ -58,11 +60,16 @@ akka { task-peeking-mode="LIFO" # based on https://www.playframework.com/documentation/2.7.x/Migration24#Thread-pool-configuration } + # https://www.playframework.com/documentation/2.7.x/ThreadPools#Highly-synchronous + thread-pool-executor { + fixed-pool-size = 44 # db conn pool (29) + number of cores (14) + housekeeping (1) + } + # https://doc.akka.io/docs/akka/2.5.11/dispatchers.html#looking-up-a-dispatcher # Throughput defines the maximum number of messages to be # processed per actor before the thread jumps to the next actor. # Set to 1 for as fair as possible. - throughput = 64 + throughput = 1 } } }