Kaynağa Gözat

Fix play2-java-ebean and play2-scala-anorm's tests

- Single query tests should return a JSON object not a list
- Multiple query tests should perform query count parameter conversion
- Run sequential queries immediately in the same thread
Rich Dougherty 11 yıl önce
ebeveyn
işleme
e9341fec1d

+ 3 - 3
play2/benchmark_config

@@ -55,7 +55,7 @@
         "versus": "netty", 
         "port": "9000", 
         "db_url": "/db", 
-        "query_url": "/db?queries="
+        "query_url": "/queries?queries="
       }, 
       "scala-anorm-linux": {
         "display_name": "play2-scala-anorm-linux", 
@@ -74,7 +74,7 @@
         "versus": "netty", 
         "port": "9000", 
         "db_url": "/db", 
-        "query_url": "/db?queries=", 
+        "query_url": "/queries?queries=", 
         "fortune_url": "/fortunes", 
         "update_url": "/update?queries="
       }, 
@@ -95,7 +95,7 @@
         "versus": "netty", 
         "port": "9000", 
         "db_url": "/db", 
-        "query_url": "/db?queries=", 
+        "query_url": "/queries?queries=", 
         "fortune_url": "/fortunes", 
         "update_url": "/update?queries="
       }

+ 1 - 1
play2/generate_config.py

@@ -19,7 +19,7 @@ configurations = [
 test_urls = {
   'json': '/json',
   'db': '/db',
-  'query': '/db?queries=',
+  'query': '/queries?queries=',
   'fortune': '/fortunes',
   'update': '/update?queries=',
 }

+ 44 - 18
play2/play2-java-ebean/app/controllers/Application.java

@@ -35,32 +35,42 @@ public class Application extends Controller {
             new NamedThreadFactory("dbEc"));
     private static final ExecutionContext dbEc = ExecutionContexts.fromExecutorService(tpe);
 
-    // A predicate for checking our ability to service database requests is determined by ensuring that the request
-    // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
-    // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
-    // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
-    // adequate in order to throttle the acceptance of requests to the size of the pool.
+    // If the thread-pool used by the database grows too large then our server
+    // is probably struggling, and we should start dropping requests. Set
+    // the max size of our queue something above the number of concurrent
+    // connections that we need to handle.
     public static class IsDbAvailable implements Predicate {
         @Override
         public boolean condition() {
-            return tpe.getQueue().size() < maxConnections * MAX_QUERIES_PER_REQUEST;
+            return tpe.getQueue().size() <= 1024;
         }
     }
 
     @Predicated(predicate = IsDbAvailable.class, failed = SERVICE_UNAVAILABLE)
-    public static F.Promise<Result> db(final Integer queries) {
-        final Random random = ThreadLocalRandom.current();
-        final List<F.Promise<? extends World>> promises = new ArrayList<F.Promise<? extends World>>(queries);
-        for (int i = 0; i < queries; ++i) {
-            final F.Promise<World> p = F.Promise.promise(new F.Function0<World>() {
-                @Override
-                public World apply() throws Throwable {
-                    return World.find.byId(Long.valueOf(random.nextInt(TEST_DATABASE_ROWS) + 1));
-                }
-            }, dbEc);
-            promises.add(p);
+    public static F.Promise<Result> db() {
+        return getRandomWorlds(1).map(new F.Function<List<World>, Result>() {
+            @Override
+            public Result apply(List<World> worlds) {
+                return ok(Json.toJson(worlds.get(0)));
+            }
+        });
+    }
+
+    @Predicated(predicate = IsDbAvailable.class, failed = SERVICE_UNAVAILABLE)
+    public static F.Promise<Result> queries(final String queryCountString) {
+        int queryCount;
+        try {
+            queryCount = Integer.parseInt(queryCountString, 10);
+        } catch (NumberFormatException e) {
+            queryCount = 1;
         }
-        return F.Promise.sequence(promises).map(new F.Function<List<World>, Result>() {
+        if (queryCount < 1) {
+            queryCount = 1;
+        } else if (queryCount > 500) {
+            queryCount = 500;
+        }
+
+        return getRandomWorlds(queryCount).map(new F.Function<List<World>, Result>() {
             @Override
             public Result apply(List<World> worlds) {
                 return ok(Json.toJson(worlds));
@@ -68,4 +78,20 @@ public class Application extends Controller {
         });
     }
 
+    private static F.Promise<List<World>> getRandomWorlds(final int n) {
+        return F.Promise.promise(new F.Function0<List<World>>() {
+            @Override
+            public List<World> apply() {
+                Random random = ThreadLocalRandom.current();
+                List<World> worlds = new ArrayList<World>(n);
+                for (int i = 0; i < n; ++i) {
+                    long randomId = random.nextInt(TEST_DATABASE_ROWS) + 1;
+                    World world = World.find.byId(randomId);
+                    worlds.add(world);
+                }
+                return worlds;
+            }
+        }, dbEc);
+    }
+
 }

+ 2 - 1
play2/play2-java-ebean/conf/routes

@@ -3,7 +3,8 @@
 # ~~~~
 
 # Home page
-GET     /db                             controllers.Application.db(queries: Int ?= 1)
+GET     /db                             controllers.Application.db()
+GET     /queries                        controllers.Application.queries(queries ?= "1")
 
 # Map static resources from the /public folder to the /assets URL path
 GET     /assets/*file               controllers.Assets.at(path="/public", file)

+ 58 - 40
play2/play2-scala-anorm/app/controllers/Application.scala

@@ -1,6 +1,7 @@
 package controllers
 
 import play.api.Play.current
+import play.api.db.DB
 import play.api.mvc._
 import play.api.libs.json.Json
 import java.util.concurrent._
@@ -14,7 +15,6 @@ import play.core.NamedThreadFactory
 
 object Application extends Controller {
 
-  private val MaxQueriesPerRequest = 20
   private val TestDatabaseRows = 10000
 
   private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
@@ -29,60 +29,78 @@ object Application extends Controller {
     new NamedThreadFactory("dbEc"))
   private val dbEc = ExecutionContext.fromExecutorService(tpe)
 
-  // A predicate for checking our ability to service database requests is determined by ensuring that the request
-  // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
-  // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
-  // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
-  // adequate in order to throttle the acceptance of requests to the size of the pool.
-  def isDbAvailable: Boolean = tpe.getQueue.size() < maxConnections * MaxQueriesPerRequest
+  // If the thread-pool used by the database grows too large then our server
+  // is probably struggling, and we should start dropping requests. Set
+  // the max size of our queue something above the number of concurrent
+  // connections that we need to handle.
+  def isDbQueueTooBig: Boolean = tpe.getQueue.size() <= 1024
 
-  def db(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
+  def db = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
     Action.async {
-      val random = ThreadLocalRandom.current()
-
-      val worlds = Future.sequence((for {
-        _ <- 1 to queries
-      } yield Future(World.findById(random.nextInt(TestDatabaseRows) + 1))(dbEc)
-        ).toList)
-
-      worlds map {
-        w => Ok(Json.toJson(w))
+      getRandomWorlds(1).map { worlds =>
+        Ok(Json.toJson(worlds.head))
       }
     }
   }
 
-  def fortunes() = PredicatedAction(isDbAvailable, ServiceUnavailable) {
+  def queries(countString: String) = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
     Action.async {
-      Future(Fortune.getAll())(dbEc).map { fs =>
-        val fortunes =  Fortune(0.toLong, "Additional fortune added at request time.") +: fs
-        Ok(views.html.fortune(fortunes))
+      val n = parseCount(countString)
+      getRandomWorlds(n).map { worlds =>
+        Ok(Json.toJson(worlds))
       }
     }
   }
 
-  def update(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
-    Action.async {
-      val random = ThreadLocalRandom.current()
-
-      val boundsCheckedQueries = queries match {
-        case q if q > 500 => 500
-        case q if q <   1 => 1
-        case _ => queries
+  private def parseCount(s: String): Int = {
+    try {
+      val parsed = java.lang.Integer.parseInt(s, 10)
+      parsed match {
+        case i if i < 1 => 1
+        case i if i > 500 => 500
+        case i => i
       }
+    } catch {
+      case _: NumberFormatException => 1
+    }
+  }
 
-      val worlds = Future.sequence((for {
-        _ <- 1 to boundsCheckedQueries
-      } yield Future {
-          val world = World.findById(random.nextInt(TestDatabaseRows) + 1)
-          val updatedWorld = world.copy(randomNumber = random.nextInt(TestDatabaseRows) + 1)
-          World.updateRandom(updatedWorld)
-          updatedWorld
-        }(dbEc)
-      ).toList)
+  private def getRandomWorlds(n: Int): Future[Seq[World]] = Future {
+    val random = ThreadLocalRandom.current()
+    DB.withConnection { implicit connection =>
+      for (_ <- 1 to n) yield {
+        val randomId: Long = random.nextInt(TestDatabaseRows) + 1
+        World.findById(randomId)
+      }
+    }
+  }(dbEc)
 
-      worlds.map {
-        w => Ok(Json.toJson(w)).withHeaders("Server" -> "Netty")
+  def fortunes() = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
+    Action.async {
+      Future {
+        val fortunes = Fortune.getAll()
+        val extendedFortunes = Fortune(0.toLong, "Additional fortune added at request time.") +: fortunes
+        Ok(views.html.fortune(extendedFortunes))
       }
     }
   }
+
+  def update(countString: String) = PredicatedAction(isDbQueueTooBig, ServiceUnavailable) {
+    Action.async {
+      val n = parseCount(countString)
+      Future {
+        val random = ThreadLocalRandom.current()
+        val worlds = DB.withConnection { implicit connection =>
+          for(_ <- 1 to n) yield {
+            val randomId: Long = random.nextInt(TestDatabaseRows) + 1
+            val world = World.findById(random.nextInt(TestDatabaseRows) + 1)
+            val updatedWorld = world.copy(randomNumber = random.nextInt(10000) + 1)
+            World.updateRandom(updatedWorld)
+            updatedWorld
+          }
+        }
+        Ok(Json.toJson(worlds)).withHeaders("Server" -> "Netty")
+      }(dbEc)
+    }
+  }
 }

+ 6 - 5
play2/play2-scala-anorm/app/models/World.scala

@@ -1,11 +1,12 @@
 package models
 
-import play.api.db._
-import play.api.Play.current
 import anorm._
 import anorm.SqlParser._
-import play.api.libs.json._
+import java.sql.Connection
+import play.api.db._
 import play.api.libs.functional.syntax._
+import play.api.libs.json._
+import play.api.Play.current
 
 case class World(id: Pk[Long], randomNumber: Long)
 
@@ -35,7 +36,7 @@ object World {
   /**
    * Retrieve a World by id.
    */
-  def findById(id: Long): World = {
+  def findById(id: Long)(implicit connection: Connection): World = {
     DB.withConnection { implicit connection =>
       SQL("SELECT * FROM World WHERE id = {id}").on(
           "id" -> id
@@ -43,7 +44,7 @@ object World {
     }
   }
 
-  def updateRandom(world: World) {
+  def updateRandom(world: World)(implicit connection: Connection) {
     DB.withConnection { implicit connection =>
       SQL("UPDATE World SET randomNumber = {randomNumber} WHERE id = {id}").on(
         "id" -> world.id.get,

+ 3 - 2
play2/play2-scala-anorm/conf/routes

@@ -3,9 +3,10 @@
 # ~~~~
 
 # Home page
-GET     /db                             controllers.Application.db(queries: Int ?= 1)
+GET     /db                             controllers.Application.db
+GET     /queries                        controllers.Application.queries(queries ?= "1")
 GET     /fortunes                       controllers.Application.fortunes
-GET     /update                         controllers.Application.update(queries: Int ?= 1)
+GET     /update                         controllers.Application.update(queries ?= "1")
 
 # Map static resources from the /public folder to the /assets URL path
 GET     /assets/*file                   controllers.Assets.at(path="/public", file)