Forráskód Böngészése

PG Client driver implementation for OfficeFloor (#6598)

* Removing Rapidoid as seems dead project (no commit last year)

* Downloading from SourceForge

* Bump maven-compiler-plugin in /frameworks/Java/officefloor/src

Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.8.0 to 3.8.1.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.0...maven-compiler-plugin-3.8.1)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump maven-shade-plugin in /frameworks/Java/officefloor/src

Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.1...maven-shade-plugin-3.2.2)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Fixing Raw OfficeFloor

* Specifying Spring plugin version

* Bump to OfficeFloor 3.21.0

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.4.RELEASE to 2.2.5.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.4.RELEASE...v2.2.5.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.5.RELEASE to 2.2.6.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.5.RELEASE...v2.2.6.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.21.0 to 3.22.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump maven-shade-plugin in /frameworks/Java/officefloor/src

Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.2...maven-shade-plugin-3.2.3)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.22.0 to 3.23.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.23.0 to 3.24.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.6.RELEASE to 2.2.7.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.6.RELEASE...v2.2.7.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.2.7.RELEASE to 2.3.0.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.2.7.RELEASE...v2.3.0.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.24.0 to 3.25.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump maven-shade-plugin in /frameworks/Java/officefloor/src

Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.3...maven-shade-plugin-3.2.4)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.0.RELEASE to 2.3.1.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.0.RELEASE...v2.3.1.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.1.RELEASE to 2.3.2.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.1.RELEASE...v2.3.2.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.25.0 to 3.26.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/compare/release-3.25.0...release-3.26.0)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.2.RELEASE to 2.3.3.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.2.RELEASE...v2.3.3.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.26.0 to 3.27.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.3.RELEASE to 2.3.4.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.3.RELEASE...v2.3.4.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.27.0 to 3.28.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Fixing to run with v3.28.0

* Including Undertow

* Using slim docker images

* Fixing to add all supported tests

* Increasing connection pool size

* Write up the 503 errors and why

* Fix grammer

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.4.RELEASE to 2.3.5.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.4.RELEASE...v2.3.5.RELEASE)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Improving chances of full processing

Increasing max thread counts to have thread per client.  Also, shading
jars correctly.

Plus adding in DB test for raw

* Fixing raw test to have database

* Ensure random numbers to avoid cached entities

* Ensuring spring random numbers to avoid entity caching

* Adding queries for raw

* officefloor-raw queries passing

* office-raw supporting all requests

* Fixed update test to ensure updates occur before responding

* Adding officefloor-async

This will demonstrate OfficeFloor running as a single thread
asynchronous server

* Upgrading to OfficeFloor 2.38.1

* Tidy up read me

* Appropriate back pressure queue

* Handle rate limiting

* Providing appropriate throttling

* Lowering threading to handle through put

* officefloor-raw passing tests

* Passing tests

* OfficeFloor 3.28.2

* Fixing rate limiting

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.28.2 to 3.29.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Fixing for bump to 3.30.0

* Increasing max direct memory

* Increasing threshold to enable verifications to pass for officefloor-raw

* Start script to take available memory into consideration

* Max direct memory aware of available memory

Also, cleaning writer threads of buffers to avoid leaks.

* Avoiding OOM by managing disable/enable reading

* DB and Fortune passing

* Ensuring free command is available

* Removing TODOs

* Increasing reactor buffer for multiple queries

* Fixing for 3.30.1

* Passing benchmark tests

* Avoiding OOM on reactor buffer sizes

* Passing validate tests

* Using defaults from 3.30.1

* Bump to 512 threads and connections

* Reverting dockerfiles to provide appropriate parameters

* openjdk:slim for apt-get available

* Providing thread affinity to raw

* Fixing rate limit throttling

* Multiplexing queries over connections per socket

* Fixing for large queries in validate

* Fixing versions of maven and java

* Providing thread affinity of DB connection thread

* Fixing for Netty event loop thread

* Tidying up compiler warnings

* Single db pool to avoid additional threads

* Use default LoopResources

* Tidying up code for warnings

* Further tidy up of code

* Revert to latest pull request

* Using parallel GC for better throughput

* Fix up for thread local buffering improvements

* OfficeFloor fortune raw

Focus of officefloor-raw is raw performance of the OfficeFloor HTTP
server.  Using raw fortune to remove mustache overheads.

* Tidy up loop

* Server name (as per discussions)

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.3.5.RELEASE to 2.4.2.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.3.5.RELEASE...v2.4.2)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.30.2 to 3.31.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.31.0 to 3.32.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.4.2 to 2.4.3.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.4.2...v2.4.3)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.4.3 to 2.4.5.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.4.3...v2.4.5)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Upgrade to GitHub-native Dependabot

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.32.0 to 3.35.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits/release-3.35.0)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* Bump to OfficeFloor 3.35.0

* Allow bump of all versions

* Vertx server

Also, renaming officefloor-raw to officefloor-r2dbc to make way for
officefloor-sqlclient

* Rename to R2dbc (from raw)

* Bump net.officefloor:bom in /frameworks/Java/officefloor/src

Bumps [net.officefloor:bom](https://github.com/officefloor/OfficeFloor) from 3.35.0 to 3.36.0.
- [Release notes](https://github.com/officefloor/OfficeFloor/releases)
- [Commits](https://github.com/officefloor/OfficeFloor/commits)

Signed-off-by: dependabot[bot] <[email protected]>

* Fixing meta-data

* Can not propagate Dependabot to TechEmpower

* Abstract WoOF from database driver

This will allow introducing Vertx SQL Client

* Fixing db port

* Providing Vertx SQL Client implementation

* Using OfficeFloorVertx for vertx

Allows for tests to reset

* Fixing link in read me

* Reducing repetition in readme

* Swapped OfficeFloor async to use Vertx SQL Client

* Bump spring-boot-maven-plugin in /frameworks/Java/officefloor/src

Bumps [spring-boot-maven-plugin](https://github.com/spring-projects/spring-boot) from 2.4.5 to 2.5.0.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v2.4.5...v2.5.0)

Signed-off-by: dependabot[bot] <[email protected]>

* Increasing SQL Client pool size to 512

Also, fixing update to sort to avoid deadlocks

* Removing unnecessary logging

* Removing dependabot configuration

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Daniel 4 éve
szülő
commit
e363ee94e6
30 módosított fájl, 1428 hozzáadás és 785 törlés
  1. 0 10
      .github/dependabot.yml
  2. 5 5
      frameworks/Java/officefloor/README.md
  3. 22 0
      frameworks/Java/officefloor/benchmark_config.json
  4. 17 0
      frameworks/Java/officefloor/config.toml
  5. 4 0
      frameworks/Java/officefloor/officefloor-r2dbc.dockerfile
  6. 16 0
      frameworks/Java/officefloor/officefloor-sqlclient.dockerfile
  7. 7 0
      frameworks/Java/officefloor/src/pom.xml
  8. 3 7
      frameworks/Java/officefloor/src/woof_benchmark_async/pom.xml
  9. 101 131
      frameworks/Java/officefloor/src/woof_benchmark_async/src/main/java/net/officefloor/benchmark/Logic.java
  10. 36 0
      frameworks/Java/officefloor/src/woof_benchmark_async/src/main/java/net/officefloor/benchmark/SetupVertxSqlClient.java
  11. 1 0
      frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/META-INF/services/net.officefloor.vertx.sqlclient.VertxSqlPoolConfigurerServiceFactory
  12. 1 1
      frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/application.objects
  13. 7 11
      frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/application.woof
  14. 2 5
      frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/datasource.properties
  15. 2 25
      frameworks/Java/officefloor/src/woof_benchmark_r2dbc/pom.xml
  16. 155 589
      frameworks/Java/officefloor/src/woof_benchmark_r2dbc/src/main/java/net/officefloor/benchmark/R2dbcOfficeFloorMain.java
  17. 1 1
      frameworks/Java/officefloor/src/woof_benchmark_spring/pom.xml
  18. 0 0
      frameworks/Java/officefloor/src/woof_benchmark_sqlclient/.gitignore
  19. 49 0
      frameworks/Java/officefloor/src/woof_benchmark_sqlclient/pom.xml
  20. 195 0
      frameworks/Java/officefloor/src/woof_benchmark_sqlclient/src/main/java/net/officefloor/benchmark/SqlClientOfficeFloorMain.java
  21. 1 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/.gitignore
  22. 35 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/pom.xml
  23. 63 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/DatabaseOperations.java
  24. 72 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/DatabaseOperationsContext.java
  25. 26 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/DatabaseOperationsFactory.java
  26. 18 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/Fortune.java
  27. 15 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/Message.java
  28. 481 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/RawWoof.java
  29. 75 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/RawWoofThreadAffinity.java
  30. 18 0
      frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/World.java

+ 0 - 10
.github/dependabot.yml

@@ -1,10 +0,0 @@
-version: 2
-updates:
-- package-ecosystem: maven
-  directory: "/frameworks/Java/officefloor/src"
-  schedule:
-    interval: daily
-    time: "04:00"
-    timezone: Australia/Perth
-  open-pull-requests-limit: 10
-  rebase-strategy: disabled

+ 5 - 5
frameworks/Java/officefloor/README.md

@@ -1,6 +1,6 @@
 # OfficeFloor
 
-OfficeFloor provides true inversion of (coupling) control.
+OfficeFloor provides [inversion of coupling control](https://dzone.com/articles/inversion-of-coupling-control).
 
 > Inversion of Control = Dependency Injection + Continuation Injection + Thread Injection
 
@@ -35,11 +35,11 @@ More information can be found at [http://officefloor.net](http://officefloor.net
 
 # OfficeFloor Benchmark Tests
 
-OfficeFloor can use different HTTP server components:
+OfficeFloor can use different HTTP server implementations that pass to OfficeFloor inversion of coupling control for request servicing:
 
-* **officefloor-netty** : incorporating Netty to service requests passing to OfficeFloor inversion of coupling control.
-* **officefloor-undertow** : incorporating Undertow to service requests passing to OfficeFloor inversion of coupling control.
-* **officefloor-vertx** : incorporating Vertx to service requests passing to OfficeFloor inversion of coupling control.
+* **officefloor-netty** : using Netty
+* **officefloor-undertow** : using Undertow
+* **officefloor-vertx** : using Vertx
 
 OfficeFloor can also use various database clients.  These use the default HTTP server component provided by OfficeFloor:
 

+ 22 - 0
frameworks/Java/officefloor/benchmark_config.json

@@ -47,6 +47,28 @@
 				"display_name": "OfficeFloor-r2dbc",
 				"notes": ""
 			},
+			"sqlclient": {
+				"json_url": "/json",
+				"plaintext_url": "/plaintext",
+				"db_url": "/db",
+				"query_url": "/queries?queries=",
+				"fortune_url": "/fortunes",
+				"update_url": "/update?queries=",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Platform",
+				"database": "Postgres",
+				"framework": "OfficeFloor",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "raw",
+				"platform": "OfficeFloor",
+				"webserver": "WoOF",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "OfficeFloor-sqlclient",
+				"notes": ""
+			},
 			"async": {
 				"json_url": "/json",
 				"plaintext_url": "/plaintext",

+ 17 - 0
frameworks/Java/officefloor/config.toml

@@ -86,6 +86,23 @@ platform = "OfficeFloor"
 webserver = "WoOF"
 versus = "None"
 
+[sqlclient]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/update?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Platform"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "raw"
+platform = "OfficeFloor"
+webserver = "WoOF"
+versus = "None"
+
 [undertow]
 urls.plaintext = "/plaintext"
 urls.json = "/json"

+ 4 - 0
frameworks/Java/officefloor/officefloor-r2dbc.dockerfile

@@ -1,6 +1,10 @@
 FROM maven:3.6.3 as maven
 WORKDIR /officefloor
 COPY src src
+WORKDIR /officefloor/src
+RUN mvn -B -N clean install
+WORKDIR /officefloor/src/woof_benchmark_woof
+RUN mvn -B clean install
 WORKDIR /officefloor/src/woof_benchmark_r2dbc
 RUN mvn -B clean package
 

+ 16 - 0
frameworks/Java/officefloor/officefloor-sqlclient.dockerfile

@@ -0,0 +1,16 @@
+FROM maven:3.6.3 as maven
+WORKDIR /officefloor
+COPY src src
+WORKDIR /officefloor/src
+RUN mvn -B -N clean install
+WORKDIR /officefloor/src/woof_benchmark_woof
+RUN mvn -B clean install
+WORKDIR /officefloor/src/woof_benchmark_sqlclient
+RUN mvn -B clean package
+
+FROM openjdk:15-slim
+RUN apt-get update && apt-get install -y libjna-java
+WORKDIR /officefloor
+COPY --from=maven /officefloor/src/woof_benchmark_sqlclient/target/woof_benchmark_sqlclient-1.0.0.jar server.jar
+EXPOSE 8080
+CMD ["java", "-Xms2g", "-Xmx2g", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dvertx.disableMetrics=true", "-Dvertx.threadChecks=false", "-Dvertx.disableContextTimings=true", "-Dvertx.disableTCCL=true", "-jar", "server.jar"]

+ 7 - 0
frameworks/Java/officefloor/src/pom.xml

@@ -14,10 +14,12 @@
 	</properties>
 	<modules>
 		<module>woof_benchmark</module>
+		<module>woof_benchmark_woof</module>
 		<module>woof_benchmark_micro</module>
 		<module>woof_benchmark_thread_affinity</module>
 		<module>woof_benchmark_async</module>
 		<module>woof_benchmark_r2dbc</module>
+		<module>woof_benchmark_sqlclient</module>
 		<module>woof_benchmark_netty</module>
 		<module>woof_benchmark_undertow</module>
 		<module>woof_benchmark_vertx</module>
@@ -54,6 +56,11 @@
 				<artifactId>woof_benchmark_micro</artifactId>
 				<version>${project.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>net.officefloor.benchmarks</groupId>
+				<artifactId>woof_benchmark_woof</artifactId>
+				<version>${project.version}</version>
+			</dependency>
 			<dependency>
 				<groupId>com.github.spullara.mustache.java</groupId>
 				<artifactId>compiler</artifactId>

+ 3 - 7
frameworks/Java/officefloor/src/woof_benchmark_async/pom.xml

@@ -15,15 +15,11 @@
 		</dependency>
 		<dependency>
 			<groupId>net.officefloor.persistence</groupId>
-			<artifactId>officer2dbc</artifactId>
+			<artifactId>officevertx_sqlclient</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>io.r2dbc</groupId>
-			<artifactId>r2dbc-postgresql</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>io.r2dbc</groupId>
-			<artifactId>r2dbc-pool</artifactId>
+			<groupId>io.vertx</groupId>
+			<artifactId>vertx-pg-client</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>com.github.spullara.mustache.java</groupId>

+ 101 - 131
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/java/net/officefloor/benchmark/Logic.java

@@ -3,10 +3,10 @@ package net.officefloor.benchmark;
 import java.io.IOException;
 import java.io.Writer;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ThreadLocalRandom;
-import java.util.function.Consumer;
 
 import org.apache.commons.text.StringEscapeUtils;
 
@@ -14,46 +14,27 @@ import com.github.mustachejava.DefaultMustacheFactory;
 import com.github.mustachejava.Mustache;
 import com.github.mustachejava.MustacheFactory;
 
-import io.r2dbc.spi.Batch;
-import io.r2dbc.spi.R2dbcTransientResourceException;
+import io.vertx.core.CompositeFuture;
+import io.vertx.core.Future;
+import io.vertx.sqlclient.Pool;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowIterator;
+import io.vertx.sqlclient.RowSet;
+import io.vertx.sqlclient.Tuple;
 import lombok.AllArgsConstructor;
 import lombok.Data;
-import net.officefloor.frame.api.escalate.AsynchronousFlowTimedOutEscalation;
 import net.officefloor.frame.api.function.AsynchronousFlow;
-import net.officefloor.frame.api.function.FlowCallback;
-import net.officefloor.plugin.clazz.FlowInterface;
-import net.officefloor.r2dbc.R2dbcSource;
-import net.officefloor.server.http.HttpException;
 import net.officefloor.server.http.HttpHeaderValue;
 import net.officefloor.server.http.HttpResponse;
-import net.officefloor.server.http.HttpStatus;
 import net.officefloor.server.http.ServerHttpConnection;
 import net.officefloor.web.HttpQueryParameter;
 import net.officefloor.web.ObjectResponse;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
 
 /**
  * Logic.
  */
 public class Logic {
 
-	static {
-		// Increase the buffer size
-		System.setProperty("reactor.bufferSize.small", String.valueOf(512));
-	}
-
-	private static final FlowCallback callback = (error) -> {
-		if (error == null) {
-			return;
-		} else if (error instanceof AsynchronousFlowTimedOutEscalation) {
-			throw new HttpException(HttpStatus.SERVICE_UNAVAILABLE);
-		} else {
-			System.out.println("ERROR: " + error.getClass().getName());
-			throw error;
-		}
-	};
-
 	/**
 	 * {@link Mustache} for /fortunes.
 	 */
@@ -97,98 +78,86 @@ public class Logic {
 	@Data
 	@AllArgsConstructor
 	public class World {
-
 		private int id;
-
 		private int randomNumber;
 	}
 
-	@FlowInterface
-	public static interface DbFlows {
-		void dbService(FlowCallback callback);
-	}
-
-	public void db(DbFlows flows) {
-		flows.dbService(callback);
-	}
-
-	public void dbService(AsynchronousFlow async, R2dbcSource source, ObjectResponse<World> response)
-			throws SQLException {
-		source.getConnection().flatMap(
-				connection -> Mono.from(connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
-						.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute()))
-				.flatMap(result -> Mono.from(result.map((row, metadata) -> {
-					Integer id = row.get(0, Integer.class);
-					Integer number = row.get(1, Integer.class);
-					return new World(id, number);
-				}))).subscribe(world -> async.complete(() -> {
-					response.send(world);
-				}), handleError(async));
+	public void db(AsynchronousFlow async, Pool pool, ObjectResponse<World> response) throws SQLException {
+		Future<World> future = pool.withConnection((connection) -> {
+			return connection.preparedQuery("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID=$1")
+					.execute(Tuple.of(ThreadLocalRandom.current().nextInt(1, 10001))).map((rowSet) -> {
+						RowIterator<Row> rows = rowSet.iterator();
+						if (!rows.hasNext()) {
+							return null;
+						}
+						Row row = rows.next();
+						return new World(row.getInteger(0), row.getInteger(1));
+					});
+		});
+		complete(async, future, (world) -> response.send(world));
 	}
 
 	// ========== QUERIES ==================
 
-	@FlowInterface
-	public static interface QueriesFlows {
-		void queriesService(FlowCallback callback);
-	}
-
-	public void queries(QueriesFlows flows) {
-		flows.queriesService(callback);
-	}
-
-	public void queriesService(@HttpQueryParameter("queries") String queries, AsynchronousFlow async,
-			R2dbcSource source, ObjectResponse<List<World>> response) {
+	public void queries(@HttpQueryParameter("queries") String queries, AsynchronousFlow async, Pool pool,
+			ObjectResponse<List<World>> response) {
 		int queryCount = getQueryCount(queries);
-		source.getConnection().flatMap(connection -> {
-			return Flux.range(1, queryCount)
-					.flatMap(index -> connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
-							.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
-					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						Integer number = row.get(1, Integer.class);
-						return new World(id, number);
-					}))).collectList();
-		}).subscribe(worlds -> async.complete(() -> {
-			response.send(worlds);
-		}), handleError(async));
+		Future<CompositeFuture> future = pool.withConnection((connection) -> {
+			@SuppressWarnings("rawtypes")
+			List<Future> futures = new ArrayList<>(queryCount);
+			for (int i = 0; i < queryCount; i++) {
+				futures.add(connection.preparedQuery("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID=$1")
+						.execute(Tuple.of(ThreadLocalRandom.current().nextInt(1, 10001))).map((rowSet) -> {
+							RowIterator<Row> rows = rowSet.iterator();
+							if (!rows.hasNext()) {
+								return null;
+							}
+							Row row = rows.next();
+							return new World(row.getInteger(0), row.getInteger(1));
+						}));
+			}
+			return CompositeFuture.all(futures);
+		});
+		complete(async, future, (worlds) -> response.send(worlds.list()));
 	}
 
 	// =========== UPDATES ===================
 
-	@FlowInterface
-	public static interface UpdateFlows {
-		void updateService(FlowCallback callback);
-	}
-
-	public void update(UpdateFlows flows) {
-		flows.updateService(callback);
-	}
-
-	public void updateService(@HttpQueryParameter("queries") String queries, AsynchronousFlow async, R2dbcSource source,
+	public void update(@HttpQueryParameter("queries") String queries, AsynchronousFlow async, Pool pool,
 			ObjectResponse<List<World>> response) {
 		int queryCount = getQueryCount(queries);
-		source.getConnection().flatMap(connection -> {
-			return Flux.range(1, queryCount)
-					.flatMap(index -> connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
-							.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
-					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						Integer number = row.get(1, Integer.class);
-						return new World(id, number);
-					}))).collectList().flatMap(worlds -> {
-						Collections.sort(worlds, (a, b) -> a.id - b.id);
-						Batch batch = connection.createBatch();
-						for (World world : worlds) {
-							world.randomNumber = ThreadLocalRandom.current().nextInt(1, 10001);
-							batch.add("UPDATE WORLD SET RANDOMNUMBER = " + world.randomNumber + " WHERE ID = "
-									+ world.id);
-						}
-						return Mono.from(batch.execute()).map((result) -> worlds);
-					});
-		}).subscribe(worlds -> async.complete(() -> {
-			response.send(worlds);
-		}), handleError(async));
+		Future<List<World>> future = pool.withConnection((connection) -> {
+			@SuppressWarnings("rawtypes")
+			List<Future> futures = new ArrayList<>(queryCount);
+
+			// Run queries to get the worlds
+			for (int i = 0; i < queryCount; i++) {
+				futures.add(connection.preparedQuery("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID=$1")
+						.execute(Tuple.of(ThreadLocalRandom.current().nextInt(1, 10001))).map((rowSet) -> {
+							RowIterator<Row> rows = rowSet.iterator();
+							if (!rows.hasNext()) {
+								return null;
+							}
+							Row row = rows.next();
+							return new World(row.getInteger(0), ThreadLocalRandom.current().nextInt(1, 10001));
+						}));
+			}
+			return CompositeFuture.all(futures).flatMap((compositeFuture) -> {
+				List<World> worlds = compositeFuture.list();
+
+				// Sort worlds to avoid deadlocks on updates
+				Collections.sort(worlds, (a, b) -> a.id - b.id);
+
+				// All worlds obtained, so run update
+				List<Tuple> batch = new ArrayList<>(queryCount);
+				for (World update : worlds) {
+					batch.add(Tuple.of(update.randomNumber, update.id));
+				}
+				return connection.preparedQuery("UPDATE world SET randomnumber=$1 WHERE id=$2").executeBatch(batch)
+						.map((updates) -> worlds);
+			});
+		});
+		complete(async, future, (worlds) -> response.send(worlds));
 	}
 
 	// =========== FORTUNES ==================
@@ -204,25 +173,19 @@ public class Logic {
 		private String message;
 	}
 
-	@FlowInterface
-	public static interface FortunesFlows {
-		void fortunesService(FlowCallback callback);
-	}
-
-	public void fortunes(FortunesFlows flows) {
-		flows.fortunesService(callback);
-	}
-
-	public void fortunesService(AsynchronousFlow async, R2dbcSource source, ServerHttpConnection httpConnection)
-			throws IOException, SQLException {
-		source.getConnection().flatMap(connection -> {
-			return Flux.from(connection.createStatement("SELECT ID, MESSAGE FROM FORTUNE").execute())
-					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						String message = row.get(1, String.class);
-						return new Fortune(id, message);
-					}))).collectList();
-		}).subscribe(fortunes -> async.complete(() -> {
+	public void fortunes(AsynchronousFlow async, Pool pool, ServerHttpConnection httpConnection) {
+		Future<RowSet<Row>> future = pool.withConnection((connection) -> {
+			return connection.preparedQuery("SELECT ID, MESSAGE FROM FORTUNE").execute();
+		});
+		complete(async, future, (rowSet) -> {
+
+			// Obtain the fortunes
+			List<Fortune> fortunes = new ArrayList<>(16);
+			RowIterator<Row> rows = rowSet.iterator();
+			while (rows.hasNext()) {
+				Row row = rows.next();
+				fortunes.add(new Fortune(row.getInteger(0), row.getString(1)));
+			}
 
 			// Additional fortunes
 			fortunes.add(new Fortune(0, "Additional fortune added at request time."));
@@ -232,8 +195,7 @@ public class Logic {
 			HttpResponse response = httpConnection.getResponse();
 			response.setContentType(TEXT_HTML, null);
 			this.fortuneMustache.execute(response.getEntityWriter(), fortunes);
-
-		}), handleError(async));
+		});
 	}
 
 	// =========== helper ===================
@@ -247,12 +209,20 @@ public class Logic {
 		}
 	}
 
-	private static Consumer<Throwable> handleError(AsynchronousFlow async) {
-		return (error) -> async.complete(() -> {
-			try {
-				throw error;
-			} catch (R2dbcTransientResourceException | AsynchronousFlowTimedOutEscalation overloadEx) {
-				throw new HttpException(HttpStatus.SERVICE_UNAVAILABLE);
+	@FunctionalInterface
+	private static interface Completion<T> {
+		void complete(T result) throws Exception;
+	}
+
+	private static <T> void complete(AsynchronousFlow async, Future<T> future, Completion<T> writeResponse) {
+		future.onComplete(result -> {
+			if (result.failed()) {
+				async.complete(() -> {
+					result.cause().printStackTrace();
+					throw result.cause();
+				});
+			} else {
+				async.complete(() -> writeResponse.complete(result.result()));
 			}
 		});
 	}

+ 36 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/java/net/officefloor/benchmark/SetupVertxSqlClient.java

@@ -0,0 +1,36 @@
+package net.officefloor.benchmark;
+
+import io.vertx.sqlclient.PoolOptions;
+import net.officefloor.frame.api.source.ServiceContext;
+import net.officefloor.vertx.sqlclient.VertxSqlPoolConfigurer;
+import net.officefloor.vertx.sqlclient.VertxSqlPoolConfigurerContext;
+import net.officefloor.vertx.sqlclient.VertxSqlPoolConfigurerServiceFactory;
+
+/**
+ * Sets up the {@link PoolOptions}.
+ * 
+ * @author Daniel Sagenschneider
+ */
+public class SetupVertxSqlClient implements VertxSqlPoolConfigurer, VertxSqlPoolConfigurerServiceFactory {
+
+	/*
+	 * ================ VertxSqlPoolConfigurerServiceFactory =================
+	 */
+
+	@Override
+	public VertxSqlPoolConfigurer createService(ServiceContext context) throws Throwable {
+		return this;
+	}
+
+	/*
+	 * ======================= VertxSqlPoolConfigurer =========================
+	 */
+
+	@Override
+	public void configure(VertxSqlPoolConfigurerContext context) throws Exception {
+		final int MAX_POOL_SIZE = 512;
+		System.out.println("Setting max pool size to " + MAX_POOL_SIZE);
+		context.getPoolOptions().setMaxSize(MAX_POOL_SIZE);
+	}
+
+}

+ 1 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/META-INF/services/net.officefloor.vertx.sqlclient.VertxSqlPoolConfigurerServiceFactory

@@ -0,0 +1 @@
+net.officefloor.benchmark.SetupVertxSqlClient

+ 1 - 1
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/application.objects

@@ -1,6 +1,6 @@
 <objects>
 
-	<managed-object source="net.officefloor.r2dbc.R2dbcManagedObjectSource">
+	<managed-object source="net.officefloor.vertx.sqlclient.VertxSqlPoolManagedObjectSource">
 		<property-file path="datasource.properties" />
 	</managed-object>
 

+ 7 - 11
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/application.woof

@@ -1,22 +1,22 @@
 <woof>
   <http-continuations>
     <http-continuation path="/db" secure="false" x="87" y="172">
-      <section name="Logic" input="db"/>
+      <section name="Section" input="db"/>
     </http-continuation>
     <http-continuation path="/fortunes" secure="false" x="78" y="213">
-      <section name="Logic" input="fortunes"/>
+      <section name="Section" input="fortunes"/>
     </http-continuation>
     <http-continuation path="/json" secure="false" x="84" y="123">
-      <section name="Logic" input="json"/>
+      <section name="Section" input="json"/>
     </http-continuation>
     <http-continuation path="/plaintext" secure="false" x="84" y="66">
-      <section name="Logic" input="plaintext"/>
+      <section name="Section" input="plaintext"/>
     </http-continuation>
     <http-continuation path="/queries" secure="false" x="82" y="270">
-      <section name="Logic" input="queries"/>
+      <section name="Section" input="queries"/>
     </http-continuation>
     <http-continuation path="/update" secure="false" x="82" y="323">
-      <section name="Logic" input="update"/>
+      <section name="Section" input="update"/>
     </http-continuation>
   </http-continuations>
   <http-inputs>
@@ -24,17 +24,13 @@
   <templates>
   </templates>
   <sections>
-    <section name="Logic" source="net.officefloor.plugin.section.clazz.ClassSectionSource" location="net.officefloor.benchmark.Logic" x="281" y="140">
+    <section name="Section" source="net.officefloor.plugin.section.clazz.ClassSectionSource" location="net.officefloor.benchmark.Logic" x="376" y="115">
       <input name="db" parameter-type=""/>
-      <input name="dbService" parameter-type=""/>
       <input name="fortunes" parameter-type=""/>
-      <input name="fortunesService" parameter-type=""/>
       <input name="json" parameter-type=""/>
       <input name="plaintext" parameter-type=""/>
       <input name="queries" parameter-type=""/>
-      <input name="queriesService" parameter-type=""/>
       <input name="update" parameter-type=""/>
-      <input name="updateService" parameter-type=""/>
     </section>
   </sections>
   <procedures>

+ 2 - 5
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/datasource.properties

@@ -1,8 +1,5 @@
-driver=pool
-protocol=postgresql
 host=tfb-database
 port=5432
 database=hello_world
-user=benchmarkdbuser
-password=benchmarkdbpass
-maxSize=512
+username=benchmarkdbuser
+password=benchmarkdbpass

+ 2 - 25
frameworks/Java/officefloor/src/woof_benchmark_r2dbc/pom.xml

@@ -10,16 +10,8 @@
 	<artifactId>woof_benchmark_r2dbc</artifactId>
 	<dependencies>
 		<dependency>
-			<groupId>net.officefloor.web</groupId>
-			<artifactId>woof</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.fasterxml.jackson.core</groupId>
-			<artifactId>jackson-databind</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.fasterxml.jackson.module</groupId>
-			<artifactId>jackson-module-afterburner</artifactId>
+			<groupId>net.officefloor.benchmarks</groupId>
+			<artifactId>woof_benchmark_woof</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>io.r2dbc</groupId>
@@ -29,21 +21,6 @@
 			<groupId>io.r2dbc</groupId>
 			<artifactId>r2dbc-pool</artifactId>
 		</dependency>
-		<dependency>
-			<groupId>net.officefloor.web</groupId>
-			<artifactId>officeweb_executive</artifactId>
-			<exclusions>
-				<exclusion>
-					<groupId>net.officefloor.web</groupId>
-					<artifactId>officeweb</artifactId>
-				</exclusion>
-			</exclusions>
-		</dependency>
-		<dependency>
-			<groupId>org.projectlombok</groupId>
-			<artifactId>lombok</artifactId>
-			<scope>provided</scope>
-		</dependency>
 	</dependencies>
 	<build>
 		<plugins>

+ 155 - 589
frameworks/Java/officefloor/src/woof_benchmark_r2dbc/src/main/java/net/officefloor/benchmark/R2dbcOfficeFloorMain.java

@@ -1,101 +1,31 @@
-/*
- * OfficeFloor - http://www.officefloor.net
- * Copyright (C) 2005-2018 Daniel Sagenschneider
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
 package net.officefloor.benchmark;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.Writer;
-import java.nio.ByteBuffer;
-import java.nio.channels.CancelledKeyException;
-import java.nio.channels.ClosedChannelException;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
 import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Logger;
-
-import org.apache.commons.text.StringEscapeUtils;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
 
+import io.netty.channel.unix.Socket;
 import io.r2dbc.pool.PoolingConnectionFactoryProvider;
 import io.r2dbc.spi.Batch;
 import io.r2dbc.spi.Connection;
 import io.r2dbc.spi.ConnectionFactories;
 import io.r2dbc.spi.ConnectionFactory;
 import io.r2dbc.spi.ConnectionFactoryOptions;
-import io.r2dbc.spi.R2dbcTransientResourceException;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import net.officefloor.benchmark.R2dbcOfficeFloorMain.Fortune;
-import net.officefloor.benchmark.R2dbcOfficeFloorMain.Message;
-import net.officefloor.benchmark.R2dbcOfficeFloorMain.World;
-import net.officefloor.frame.api.manage.OfficeFloor;
-import net.officefloor.frame.api.manage.ProcessManager;
-import net.officefloor.frame.api.managedobject.ManagedObjectContext;
-import net.officefloor.frame.api.managedobject.ProcessSafeOperation;
-import net.officefloor.frame.api.managedobject.pool.ThreadCompletionListener;
 import net.officefloor.server.RequestHandler;
-import net.officefloor.server.SocketManager;
-import net.officefloor.server.SocketServicer;
-import net.officefloor.server.http.AbstractHttpServicerFactory;
-import net.officefloor.server.http.HttpHeaderName;
-import net.officefloor.server.http.HttpHeaderValue;
-import net.officefloor.server.http.HttpRequest;
 import net.officefloor.server.http.HttpResponse;
-import net.officefloor.server.http.HttpResponseHeaders;
-import net.officefloor.server.http.HttpServerLocation;
-import net.officefloor.server.http.HttpServerSocketManagedObjectSource;
-import net.officefloor.server.http.HttpStatus;
 import net.officefloor.server.http.ServerHttpConnection;
-import net.officefloor.server.http.impl.HttpServerLocationImpl;
-import net.officefloor.server.http.impl.ProcessAwareServerHttpConnectionManagedObject;
 import net.officefloor.server.http.parse.HttpRequestParser;
-import net.officefloor.server.http.parse.HttpRequestParser.HttpRequestParserMetaData;
-import net.officefloor.server.stream.ServerWriter;
-import net.officefloor.server.stream.impl.ThreadLocalStreamBufferPool;
-import net.officefloor.web.executive.CpuCore;
-import net.officefloor.web.executive.CpuCore.LogicalCpu;
-import net.openhft.affinity.Affinity;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Scheduler;
 import reactor.core.scheduler.Schedulers;
 
-import net.officefloor.web.executive.CpuCore;
-import net.openhft.affinity.Affinity;
-
 /**
- * <p>
- * {@link SocketManager} raw performance.
- * <p>
- * Allows determining the overhead of the {@link OfficeFloor} framework.
+ * R2DBC server.
+ * 
+ * @author Daniel Sagenschneider
  */
-public class R2dbcOfficeFloorMain {
+public class R2dbcOfficeFloorMain implements DatabaseOperations {
 
 	/**
 	 * Database query load capacity to handle validation load.
@@ -107,546 +37,203 @@ public class R2dbcOfficeFloorMain {
 	 */
 	private static final int QUERY_BUFFER_SIZE = 512;
 
-	/**
-	 * {@link SocketManager}.
-	 */
-	public static SocketManager socketManager = null;
-
-	/**
-	 * {@link Logger}.
-	 */
-	private static Logger logger = Logger.getLogger(R2dbcOfficeFloorMain.class.getName());
-
 	/**
 	 * Run application.
 	 */
 	public static void main(String[] args) throws Exception {
 
-		// Obtain the port from properties
-		int port = args.length > 0 ? Integer.parseInt(args[0]) : 8080;
-
-		// Ensure previous socket manager shutdown (typically from tests)
-		if (socketManager != null) {
-			socketManager.shutdown();
-		}
-
-		// Indicate details
-		String server = System.getProperty("OFFICE.net_officefloor_jdbc_DataSourceManagedObjectSource.server",
-				"tfb-database");
-		System.out.println("Starting server on port " + port + " talking to database " + server);
-
 		// Increase the buffer size (note: too high and cause OOM issues)
 		System.setProperty("reactor.bufferSize.small", String.valueOf(QUERY_BUFFER_SIZE));
 
-		// Create the server location
-		HttpServerLocation serverLocation = new HttpServerLocationImpl("localhost", port, -1);
-
-		// Create a thread factory per logical CPU
-		ThreadCompletionListener[] threadCompletionListenerCapture = new ThreadCompletionListener[] { null };
-		List<ThreadFactory> threadFactories = new LinkedList<>();
-		for (CpuCore cpuCore : CpuCore.getCores()) {
-			for (CpuCore.LogicalCpu logicalCpu : cpuCore.getCpus()) {
-
-				// Create thread factory for logical CPU
-				ThreadFactory boundThreadFactory = (runnable) -> new Thread(() -> {
-					ThreadLocalStreamBufferPool bufferPool = (ThreadLocalStreamBufferPool) threadCompletionListenerCapture[0];
-					try {
-						// Bind thread to logical CPU
-						Affinity.setAffinity(logicalCpu.getCpuAffinity());
-
-						// Set up for thread local buffer pooling
-						bufferPool.activeThreadLocalPooling();
-
-						// Run logic for thread
-						runnable.run();
-					} finally {
-						bufferPool.threadComplete();
-					}
-				});
+		// Run the WoOF server
+		RawWoof.run(args, (socketCount, server, port, database, username,
+				password) -> new R2dbcOfficeFloorMain(socketCount, server, port, database, username, password));
+	}
 
-				// Add the thread factory
-				threadFactories.add(boundThreadFactory);
-			}
-		}
-		ThreadFactory[] executionStrategy = threadFactories.toArray(new ThreadFactory[0]);
-		System.out.println("Using " + executionStrategy.length + " executors");
+	/**
+	 * {@link ThreadLocal} {@link RateLimit}.
+	 */
+	private final ThreadLocal<RateLimit> threadLocalRateLimit = new ThreadLocal<RateLimit>();
 
-		// Create the socket manager
-		socketManager = HttpServerSocketManagedObjectSource.createSocketManager(executionStrategy,
-				(threadCompletionListener) -> threadCompletionListenerCapture[0] = threadCompletionListener);
+	/**
+	 * {@link ThreadLocal} {@link Connection} instances.
+	 */
+	private final ThreadLocal<Connection[]> threadLocalConnections;
+
+	/**
+	 * Instantiate.
+	 * 
+	 * @param socketCount Number of server {@link Socket} instances.
+	 * @param server      Name of database server.
+	 * @param port        Port of database.
+	 * @param database    Name of database within server.
+	 * @param username    Username.
+	 * @param password    Password.
+	 */
+	public R2dbcOfficeFloorMain(int socketCount, String server, int port, String database, String username,
+			String password) {
 
 		// Must have enough connection capacity for initial load (+1 for rounding)
-		int requiredConnectionsPerSocket = (QUERY_LOAD_CAPACITY / (executionStrategy.length * QUERY_BUFFER_SIZE)) + 1;
+		int requiredConnectionsPerSocket = (QUERY_LOAD_CAPACITY / (socketCount * QUERY_BUFFER_SIZE)) + 1;
 		int connectionsPerSocket = Math.max(4, requiredConnectionsPerSocket);
 		System.out.println("Using " + connectionsPerSocket + " connections per socket");
 
 		// Determine the pool size for connections
-		int connectionPoolSize = executionStrategy.length * connectionsPerSocket;
+		int connectionPoolSize = socketCount * connectionsPerSocket;
 
 		// Build the connection pool
 		ConnectionFactoryOptions factoryOptions = ConnectionFactoryOptions.builder()
 				.option(ConnectionFactoryOptions.DRIVER, "pool").option(ConnectionFactoryOptions.PROTOCOL, "postgresql")
-				.option(ConnectionFactoryOptions.HOST, server).option(ConnectionFactoryOptions.PORT, 5432)
-				.option(ConnectionFactoryOptions.DATABASE, "hello_world")
-				.option(ConnectionFactoryOptions.USER, "benchmarkdbuser")
-				.option(ConnectionFactoryOptions.PASSWORD, "benchmarkdbpass")
+				.option(ConnectionFactoryOptions.HOST, server).option(ConnectionFactoryOptions.PORT, port)
+				.option(ConnectionFactoryOptions.DATABASE, database).option(ConnectionFactoryOptions.USER, username)
+				.option(ConnectionFactoryOptions.PASSWORD, password)
 				.option(PoolingConnectionFactoryProvider.MAX_SIZE, connectionPoolSize).build();
 		ConnectionFactory connectionFactory = ConnectionFactories.get(factoryOptions);
 
-		// Create raw HTTP servicing
-		RawHttpServicerFactory serviceFactory = new RawHttpServicerFactory(serverLocation, connectionFactory,
-				connectionsPerSocket);
-		socketManager.bindServerSocket(serverLocation.getClusterHttpPort(), null, null, serviceFactory, serviceFactory);
-
-		// Setup Date
-		ScheduledExecutorService dateTimer = Executors.newScheduledThreadPool(1);
-		dateTimer.scheduleAtFixedRate(serviceFactory.updateDate, 0, 1, TimeUnit.SECONDS);
-
-		// Start servicing
-		Runnable[] runnables = socketManager.getRunnables();
-		for (int i = 0; i < runnables.length; i++) {
-			executionStrategy[i].newThread(runnables[i]).start();
-		}
-		Thread.sleep(1000); // allow threads to start up
-
-		// Indicate running
-		System.out.println("OfficeFloor raw running on port " + serverLocation.getClusterHttpPort());
-	}
-
-	/**
-	 * Raw {@link AbstractHttpServicerFactory}.
-	 */
-	private static class RawHttpServicerFactory extends AbstractHttpServicerFactory {
-
-		private static HttpHeaderName NAME_SERVER = new HttpHeaderName("Server");
-
-		private static HttpHeaderValue VALUE_SERVER = new HttpHeaderValue("O");
-
-		private static HttpHeaderName NAME_DATE = new HttpHeaderName("Date");
-
-		private static byte[] HELLO_WORLD = "Hello, World!".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
-
-		private static final HttpHeaderValue APPLICATION_JSON = new HttpHeaderValue("application/json");
-
-		private static final HttpHeaderValue TEXT_PLAIN = new HttpHeaderValue("text/plain");
-
-		private static final HttpHeaderValue TEXT_HTML = new HttpHeaderValue("text/html;charset=utf-8");
-
-		private static final String QUERIES_PATH_PREFIX = "/queries?queries=";
-
-		private static final String UPDATE_PATH_PREFIX = "/update?queries=";
-
-		private static final byte[] TEMPLATE_START = "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"
-				.getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
-
-		private static final byte[] FORTUNE_START = "<tr><td>".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
-
-		private static final byte[] FORTUNE_MIDDLE = "</td><td>".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
-
-		private static final byte[] FORTUNE_END = "</td></tr>".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
-
-		private static final byte[] TEMPLATE_END = "</table></body></html>"
-				.getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
-
-		private static final R2dbcTransientResourceException THROTTLED = new R2dbcTransientResourceException();
-
-		/**
-		 * <code>Date</code> {@link HttpHeaderValue}.
-		 */
-		private volatile HttpHeaderValue dateHttpHeader;
-
-		private final Runnable updateDate = () -> {
-			String now = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
-			RawHttpServicerFactory.this.dateHttpHeader = new HttpHeaderValue(now);
-		};
-
-		/**
-		 * {@link ObjectMapper}.
-		 */
-		private final ObjectMapper objectMapper = new ObjectMapper();
-
-		/**
-		 * {@link ManagedObjectContext}.
-		 */
-		private static ManagedObjectContext managedObjectContext = new ManagedObjectContext() {
-
-			@Override
-			public String getBoundName() {
-				return R2dbcOfficeFloorMain.class.getSimpleName();
-			}
-
+		// Create thread local connection
+		this.threadLocalConnections = new ThreadLocal<Connection[]>() {
 			@Override
-			public Logger getLogger() {
-				return logger;
-			}
-
-			@Override
-			public <R, T extends Throwable> R run(ProcessSafeOperation<R, T> operation) throws T {
-				return operation.run();
-			}
-		};
-
-		/**
-		 * {@link ConnectionFactory}.
-		 */
-		private final ConnectionFactory connectionFactory;
-
-		/**
-		 * {@link ThreadLocal} {@link Connection} instances.
-		 */
-		private final ThreadLocal<Connection[]> threadLocalConnections;
-
-		/**
-		 * {@link ThreadLocal} {@link RateLimit}.
-		 */
-		private final ThreadLocal<RateLimit> threadLocalRateLimit = new ThreadLocal<RateLimit>();
-
-		/**
-		 * Instantiate.
-		 *
-		 * @param serverLocation       {@link HttpServerLocation}.
-		 * @param connectionFactory    {@link ConnectionFactory}.
-		 * @param connectionsPerSocket Number of DB connections per socket.
-		 */
-		public RawHttpServicerFactory(HttpServerLocation serverLocation, ConnectionFactory connectionFactory,
-				int connectionsPerSocket) {
-			super(serverLocation, false, new HttpRequestParserMetaData(100, 1000, 1000000), null, null, true);
-			this.objectMapper.registerModule(new AfterburnerModule());
-			this.connectionFactory = connectionFactory;
-
-			// Create thread local connection
-			this.threadLocalConnections = new ThreadLocal<Connection[]>() {
-				@Override
-				protected Connection[] initialValue() {
-					Connection[] connections = new Connection[connectionsPerSocket];
-					for (int i = 0; i < connections.length; i++) {
-						connections[i] = Mono.from(RawHttpServicerFactory.this.connectionFactory.create()).block();
-					}
-					return connections;
+			protected Connection[] initialValue() {
+				Connection[] connections = new Connection[connectionsPerSocket];
+				for (int i = 0; i < connections.length; i++) {
+					connections[i] = Mono.from(connectionFactory.create()).block();
 				}
-			};
-		}
-
-		/**
-		 * Sends the {@link HttpResponse}.
-		 * 
-		 * @param connection {@link ServerHttpConnection}.
-		 * @throws IOException If fails to send.
-		 */
-		protected void send(ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) throws IOException {
-			try {
-				connection.getServiceFlowCallback().run(null);
-			} catch (IOException ex) {
-				throw ex;
-			} catch (Throwable ex) {
-				throw new IOException(ex);
-			}
-		}
-
-		/*
-		 * =============== SocketServicerFactory =================
-		 */
-
-		@Override
-		public SocketServicer<HttpRequestParser> createSocketServicer(
-				RequestHandler<HttpRequestParser> requestHandler) {
-
-			// Ensure rate limits for socket servicing thread
-			// Note: will always create before servicing any requests
-			if (this.threadLocalRateLimit.get() == null) {
-				Connection[] connections = this.threadLocalConnections.get();
-				RateLimit rateLimit = new RateLimit(requestHandler, connections);
-				this.threadLocalRateLimit.set(rateLimit);
-			}
-
-			// Continue on to create socket servicer
-			return super.createSocketServicer(requestHandler);
-		}
-
-		/*
-		 * ===================== HttpServicer ====================
-		 */
-
-		@Override
-		protected ProcessManager service(ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection)
-				throws IOException {
-
-			// Configure context
-			connection.setManagedObjectContext(managedObjectContext);
-
-			// Service the connection
-			HttpRequest request = connection.getRequest();
-			HttpResponse response = connection.getResponse();
-
-			// Provider Server and Date
-			HttpResponseHeaders headers = response.getHeaders();
-			headers.addHeader(NAME_SERVER, VALUE_SERVER);
-			headers.addHeader(NAME_DATE, this.dateHttpHeader);
-
-			// Determine request
-			String requestUri = request.getUri();
-			switch (requestUri) {
-
-			case "/plaintext":
-				this.plaintext(response, connection);
-				break;
-
-			case "/json":
-				this.json(response, connection);
-				break;
-
-			case "/db":
-				this.db(response, connection);
-				break;
-
-			case "/fortunes":
-				this.fortunes(response, connection);
-				break;
-
-			default:
-				// Provide redirect
-				if (requestUri.startsWith(QUERIES_PATH_PREFIX)) {
-					this.queries(requestUri, response, connection);
-
-				} else if (requestUri.startsWith(UPDATE_PATH_PREFIX)) {
-					this.update(requestUri, response, connection);
-
-				} else {
-					// Unknown request
-					response.setStatus(HttpStatus.NOT_FOUND);
-					this.send(connection);
-				}
-				break;
+				return connections;
 			}
+		};
+	}
 
-			// No process management
-			return null;
-		}
+	/*
+	 * ===================== DatabaseOperations ======================
+	 */
 
-		private void plaintext(HttpResponse response,
-				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) throws IOException {
-			response.setContentType(TEXT_PLAIN, null);
-			response.getEntity().write(HELLO_WORLD);
-			this.send(connection);
-		}
+	@Override
+	public void threadSetup(RequestHandler<HttpRequestParser> requestHandler) {
 
-		private void json(HttpResponse response, ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection)
-				throws IOException {
-			response.setContentType(APPLICATION_JSON, null);
-			this.objectMapper.writeValue(response.getEntityWriter(), new Message("Hello, World!"));
-			this.send(connection);
+		// Ensure rate limits for socket servicing thread
+		// Note: will always create before servicing any requests
+		if (this.threadLocalRateLimit.get() == null) {
+			Connection[] connections = this.threadLocalConnections.get();
+			RateLimit rateLimit = new RateLimit(requestHandler, connections);
+			this.threadLocalRateLimit.set(rateLimit);
 		}
+	}
 
-		private void db(HttpResponse response, ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
-
-			// Determine if will overload queries
-			RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(1);
-			if (conn == null) {
-				this.sendError(connection, THROTTLED);
-				return; // rate limited
-			}
-
-			// Service
-			Mono.from(conn.connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
-					.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
-					.flatMap(result -> Mono.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						Integer number = row.get(1, Integer.class);
-						return new World(id, number);
-					}))).publishOn(conn.writeScheduler).subscribe(world -> {
-						try {
-							response.setContentType(APPLICATION_JSON, null);
-							this.objectMapper.writeValue(response.getEntityWriter(), world);
-							this.send(connection);
-						} catch (CancelledKeyException | ClosedChannelException ex) {
-							// Ignore as disconnecting client
-						} catch (IOException ex) {
-							ex.printStackTrace();
-						}
-					}, error -> {
-						this.sendError(connection, error);
-					}, () -> {
-						conn.processed(1);
-					});
-		}
-
-		private void queries(String requestUri, HttpResponse response,
-				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
-
-			// Obtain the number of queries
-			String queriesCountText = requestUri.substring(QUERIES_PATH_PREFIX.length());
-			int queryCount = getQueryCount(queriesCountText);
-
-			// Determine if will overload queries
-			RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(queryCount);
-			if (conn == null) {
-				this.sendError(connection, THROTTLED);
-				return; // rate limited
-			}
+	@Override
+	public void db(HttpResponse response, ServerHttpConnection connection, DatabaseOperationsContext context) {
 
-			// Service
-			Flux.range(1, queryCount)
-					.flatMap(
-							index -> conn.connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
-									.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
-					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						Integer number = row.get(1, Integer.class);
-						return new World(id, number);
-					}))).collectList().publishOn(conn.writeScheduler).subscribe(worlds -> {
-						try {
-							response.setContentType(APPLICATION_JSON, null);
-							this.objectMapper.writeValue(response.getEntityWriter(), worlds);
-							this.send(connection);
-						} catch (CancelledKeyException | ClosedChannelException ex) {
-							// Ignore as disconnecting client
-						} catch (IOException ex) {
-							ex.printStackTrace();
-						}
-					}, error -> {
-						this.sendError(connection, error);
-					}, () -> {
-						conn.processed(queryCount);
-					});
+		// Determine if will overload queries
+		RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(1);
+		if (conn == null) {
+			context.sendError(connection, context.getTransientResourceException());
+			return; // rate limited
 		}
 
-		private void fortunes(HttpResponse response,
-				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
+		// Service
+		Mono.from(conn.connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
+				.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
+				.flatMap(result -> Mono.from(result.map((row, metadata) -> {
+					Integer id = row.get(0, Integer.class);
+					Integer number = row.get(1, Integer.class);
+					return new World(id, number);
+				}))).publishOn(conn.writeScheduler).subscribe(world -> {
+					context.dbSend(response, connection, world);
+				}, error -> {
+					context.sendError(connection, error);
+				}, () -> {
+					conn.processed(1);
+				});
+	}
 
-			// Determine if will overload queries
-			RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(1);
-			if (conn == null) {
-				this.sendError(connection, THROTTLED);
-				return; // rate limited
-			}
+	@Override
+	public void queries(int queryCount, HttpResponse response, ServerHttpConnection connection,
+			DatabaseOperationsContext context) {
 
-			// Service
-			Flux.from(conn.connection.createStatement("SELECT ID, MESSAGE FROM FORTUNE").execute())
-					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						String message = row.get(1, String.class);
-						return new Fortune(id, message);
-					}))).collectList().publishOn(conn.writeScheduler).subscribe(fortunes -> {
-						try {
-							// Additional fortunes
-							fortunes.add(new Fortune(0, "Additional fortune added at request time."));
-							Collections.sort(fortunes, (a, b) -> a.message.compareTo(b.message));
-
-							// Send response
-							response.setContentType(TEXT_HTML, null);
-							ServerWriter writer = response.getEntityWriter();
-							writer.write(TEMPLATE_START);
-							for (Fortune fortune : fortunes) {
-								writer.write(FORTUNE_START);
-								int id = fortune.getId();
-								writer.write(Integer.valueOf(id).toString());
-								writer.write(FORTUNE_MIDDLE);
-								StringEscapeUtils.ESCAPE_HTML4.translate(fortune.getMessage(), writer);
-								writer.write(FORTUNE_END);
-							}
-							writer.write(TEMPLATE_END);
-							this.send(connection);
-						} catch (CancelledKeyException | ClosedChannelException ex) {
-							// Ignore as disconnecting client
-						} catch (IOException ex) {
-							ex.printStackTrace();
-						}
-					}, error -> {
-						this.sendError(connection, error);
-					}, () -> {
-						conn.processed(1);
-					});
+		// Determine if will overload queries
+		RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(queryCount);
+		if (conn == null) {
+			context.sendError(connection, context.getTransientResourceException());
+			return; // rate limited
 		}
 
-		private void update(String requestUri, HttpResponse response,
-				ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection) {
-
-			// Obtain the number of queries
-			String queriesCountText = requestUri.substring(UPDATE_PATH_PREFIX.length());
-			int queryCount = getQueryCount(queriesCountText);
-			int executeQueryCount = queryCount + 1; // select all and update
+		// Service
+		Flux.range(1, queryCount)
+				.flatMap(index -> conn.connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
+						.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
+				.flatMap(result -> Flux.from(result.map((row, metadata) -> {
+					Integer id = row.get(0, Integer.class);
+					Integer number = row.get(1, Integer.class);
+					return new World(id, number);
+				}))).collectList().publishOn(conn.writeScheduler).subscribe(worlds -> {
+					context.queriesSend(response, connection, worlds);
+				}, error -> {
+					context.sendError(connection, error);
+				}, () -> {
+					conn.processed(queryCount);
+				});
+	}
 
-			// Determine if will overload queries
-			RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(executeQueryCount);
-			if (conn == null) {
-				this.sendError(connection, THROTTLED);
-				return; // rate limited
-			}
+	@Override
+	public void fortunes(HttpResponse response, ServerHttpConnection connection, DatabaseOperationsContext context) {
 
-			// Service
-			Flux.range(1, queryCount)
-					.flatMap(
-							index -> conn.connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
-									.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
-					.flatMap(result -> Flux.from(result.map((row, metadata) -> {
-						Integer id = row.get(0, Integer.class);
-						Integer number = row.get(1, Integer.class);
-						return new World(id, number);
-					}))).collectList().flatMap(worlds -> {
-						Collections.sort(worlds, (a, b) -> a.id - b.id);
-						Batch batch = conn.connection.createBatch();
-						for (World world : worlds) {
-							world.randomNumber = ThreadLocalRandom.current().nextInt(1, 10001);
-							batch.add("UPDATE WORLD SET RANDOMNUMBER = " + world.randomNumber + " WHERE ID = "
-									+ world.id);
-						}
-						return Mono.from(batch.execute()).map((result) -> worlds);
-					}).publishOn(conn.writeScheduler).subscribe(worlds -> {
-						try {
-							response.setContentType(APPLICATION_JSON, null);
-							this.objectMapper.writeValue(response.getEntityWriter(), worlds);
-							this.send(connection);
-						} catch (CancelledKeyException | ClosedChannelException ex) {
-							// Ignore as disconnecting client
-						} catch (IOException ex) {
-							ex.printStackTrace();
-						}
-					}, error -> {
-						this.sendError(connection, error);
-					}, () -> {
-						conn.processed(executeQueryCount);
-					});
+		// Determine if will overload queries
+		RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(1);
+		if (conn == null) {
+			context.sendError(connection, context.getTransientResourceException());
+			return; // rate limited
 		}
 
-		private void sendError(ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection,
-				Throwable failure) {
-			try {
-
-				// Setup to send response
-				HttpResponse response = connection.getResponse();
-				response.reset();
-
-				// Determine type of error
-				if (failure instanceof R2dbcTransientResourceException) {
-
-					// Indicate overloaded
-					response.setStatus(HttpStatus.SERVICE_UNAVAILABLE);
+		// Service
+		Flux.from(conn.connection.createStatement("SELECT ID, MESSAGE FROM FORTUNE").execute())
+				.flatMap(result -> Flux.from(result.map((row, metadata) -> {
+					Integer id = row.get(0, Integer.class);
+					String message = row.get(1, String.class);
+					return new Fortune(id, message);
+				}))).collectList().publishOn(conn.writeScheduler).subscribe(fortunes -> {
+					context.fortunesSend(response, connection, fortunes);
+				}, error -> {
+					context.sendError(connection, error);
+				}, () -> {
+					conn.processed(1);
+				});
+	}
 
-				} else {
-					// Provide details of failure
-					response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
-					response.setContentType(TEXT_PLAIN, null);
-					failure.printStackTrace(new PrintWriter(response.getEntityWriter()));
-				}
+	@Override
+	public void update(int queryCount, HttpResponse response, ServerHttpConnection connection,
+			DatabaseOperationsContext context) {
 
-				// Send error response
-				this.send(connection);
+		int executeQueryCount = queryCount + 1; // select all and update
 
-			} catch (CancelledKeyException | ClosedChannelException ex) {
-				// Ignore as disconnecting client
-			} catch (IOException ex) {
-				ex.printStackTrace();
-			}
+		// Determine if will overload queries
+		RateLimitedConnection conn = this.threadLocalRateLimit.get().getAvailableConnection(executeQueryCount);
+		if (conn == null) {
+			context.sendError(connection, context.getTransientResourceException());
+			return; // rate limited
 		}
 
-		private static int getQueryCount(String queries) {
-			try {
-				int count = Integer.parseInt(queries);
-				return (count < 1) ? 1 : (count > 500) ? 500 : count;
-			} catch (NumberFormatException ex) {
-				return 1;
-			}
-		}
+		// Service
+		Flux.range(1, queryCount)
+				.flatMap(index -> conn.connection.createStatement("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID = $1")
+						.bind(0, ThreadLocalRandom.current().nextInt(1, 10001)).execute())
+				.flatMap(result -> Flux.from(result.map((row, metadata) -> {
+					Integer id = row.get(0, Integer.class);
+					Integer number = row.get(1, Integer.class);
+					return new World(id, number);
+				}))).collectList().flatMap(worlds -> {
+					Collections.sort(worlds, (a, b) -> a.id - b.id);
+					Batch batch = conn.connection.createBatch();
+					for (World world : worlds) {
+						world.randomNumber = ThreadLocalRandom.current().nextInt(1, 10001);
+						batch.add("UPDATE WORLD SET RANDOMNUMBER = " + world.randomNumber + " WHERE ID = " + world.id);
+					}
+					return Mono.from(batch.execute()).map((result) -> worlds);
+				}).publishOn(conn.writeScheduler).subscribe(worlds -> {
+					context.updateSend(response, connection, worlds);
+				}, error -> {
+					context.sendError(connection, error);
+				}, () -> {
+					conn.processed(executeQueryCount);
+				});
 	}
 
 	private static class RateLimit {
@@ -710,25 +297,4 @@ public class R2dbcOfficeFloorMain {
 		}
 	}
 
-	@Data
-	public static class Message {
-		private final String message;
-	}
-
-	@Data
-	@AllArgsConstructor
-	public static class World {
-
-		private final int id;
-
-		private int randomNumber;
-	}
-
-	@Data
-	public static class Fortune {
-
-		private final int id;
-
-		private final String message;
-	}
 }

+ 1 - 1
frameworks/Java/officefloor/src/woof_benchmark_spring/pom.xml

@@ -39,7 +39,7 @@
 			<plugin>
 				<groupId>org.springframework.boot</groupId>
 				<artifactId>spring-boot-maven-plugin</artifactId>
-				<version>2.4.5</version>
+				<version>2.5.0</version>
 				<configuration>
 					<mainClass>net.officefloor.OfficeFloorMain</mainClass>
 					<classifier>exec</classifier>

+ 0 - 0
frameworks/Java/officefloor/src/woof_benchmark_raw/.gitignore → frameworks/Java/officefloor/src/woof_benchmark_sqlclient/.gitignore


+ 49 - 0
frameworks/Java/officefloor/src/woof_benchmark_sqlclient/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.officefloor.benchmarks</groupId>
+		<artifactId>benchmarks</artifactId>
+		<version>1.0.0</version>
+	</parent>
+	<artifactId>woof_benchmark_sqlclient</artifactId>
+	<dependencies>
+		<dependency>
+			<groupId>net.officefloor.benchmarks</groupId>
+			<artifactId>woof_benchmark_woof</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.officefloor.vertx</groupId>
+			<artifactId>officevertx</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.vertx</groupId>
+			<artifactId>vertx-pg-client</artifactId>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+						<configuration>
+							<transformers>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+									<mainClass>net.officefloor.benchmark.SqlClientOfficeFloorMain</mainClass>
+								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
+							</transformers>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 195 - 0
frameworks/Java/officefloor/src/woof_benchmark_sqlclient/src/main/java/net/officefloor/benchmark/SqlClientOfficeFloorMain.java

@@ -0,0 +1,195 @@
+package net.officefloor.benchmark;
+
+import io.vertx.core.Vertx;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgConnection;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowIterator;
+import io.vertx.sqlclient.SqlConnection;
+import io.vertx.sqlclient.Tuple;
+import net.officefloor.server.RequestHandler;
+import net.officefloor.server.http.HttpResponse;
+import net.officefloor.server.http.ServerHttpConnection;
+import net.officefloor.server.http.parse.HttpRequestParser;
+import net.officefloor.vertx.OfficeFloorVertx;
+
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * R2DBC server.
+ *
+ * @author Daniel Sagenschneider
+ */
+public class SqlClientOfficeFloorMain implements DatabaseOperations {
+
+    /**
+     * Run application.
+     */
+    public static void main(String[] args) throws Exception {
+        RawWoof.run(args, (socketCount, server, port, database, username,
+                           password) -> new SqlClientOfficeFloorMain(socketCount, server, port, database, username, password));
+    }
+
+    /**
+     * {@link ThreadLocal} {@link PgConnection} instances.
+     */
+    private final ThreadLocal<PgConnection> threadLocalConnection;
+
+    /**
+     * Instantiate.
+     *
+     * @param socketCount Number of server {@link Socket} instances.
+     * @param server      Name of database server.
+     * @param port        Port of database.
+     * @param database    Name of database within server.
+     * @param username    Username.
+     * @param password    Password.
+     */
+    public SqlClientOfficeFloorMain(int socketCount, String server, int port, String database, String username,
+                                    String password) {
+    	
+    	// Obtain the vertx
+    	Vertx vertx = OfficeFloorVertx.getVertx();
+
+        // Create connection
+        PgConnectOptions connectOptions = new PgConnectOptions().setHost(server).setPort(port).setDatabase(database)
+                .setUser(username).setPassword(password);
+
+        // Create thread local connection
+        this.threadLocalConnection = new ThreadLocal<PgConnection>() {
+            @Override
+            protected PgConnection initialValue() {
+                try {
+                    return OfficeFloorVertx.block(PgConnection.connect(vertx, connectOptions));
+                } catch (Exception ex) {
+                    throw new IllegalStateException("Failed to setup connection", ex);
+                }
+            }
+        };
+    }
+
+    /*
+     * ===================== DatabaseOperations ======================
+     */
+
+    @Override
+    public void threadSetup(RequestHandler<HttpRequestParser> requestHandler) {
+        // Nothing thread specific to set up
+    }
+
+    @Override
+    public void db(HttpResponse response, ServerHttpConnection connection, DatabaseOperationsContext context) {
+        this.threadLocalConnection.get().preparedQuery("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID=$1")
+                .execute(Tuple.of(ThreadLocalRandom.current().nextInt(1, 10001)),
+                        result -> {
+                            if (result.failed()) {
+                                context.sendError(connection, result.cause());
+                            } else {
+                                RowIterator<Row> rows = result.result().iterator();
+                                if (!rows.hasNext()) {
+                                    context.sendError(connection, 404);
+                                } else {
+                                    Row row = rows.next();
+                                    World world = new World(row.getInteger(0), row.getInteger(1));
+                                    context.dbSend(response, connection, world);
+                                }
+                            }
+                        });
+    }
+
+    @Override
+    public void queries(int queryCount, HttpResponse response, ServerHttpConnection connection,
+                        DatabaseOperationsContext context) {
+        Queue<World> worlds = new ConcurrentLinkedDeque<>();
+        SqlConnection sqlConnection = this.threadLocalConnection.get();
+        for (int i = 0; i < queryCount; i++) {
+            sqlConnection.preparedQuery("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID=$1")
+                    .execute(Tuple.of(ThreadLocalRandom.current().nextInt(1, 10001)),
+                            result -> {
+                                if (result.failed()) {
+                                    context.sendError(connection, result.cause());
+                                } else {
+                                    RowIterator<Row> rows = result.result().iterator();
+                                    if (!rows.hasNext()) {
+                                        context.sendError(connection, 404);
+                                    } else {
+                                        Row row = rows.next();
+                                        World world = new World(row.getInteger(0), row.getInteger(1));
+                                        worlds.add(world);
+
+                                        if (worlds.size() == queryCount) {
+                                            context.queriesSend(response, connection, new ArrayList<>(worlds));
+                                        }
+                                    }
+                                }
+                            });
+        }
+    }
+
+    @Override
+    public void fortunes(HttpResponse response, ServerHttpConnection connection, DatabaseOperationsContext context) {
+        this.threadLocalConnection.get().preparedQuery("SELECT ID, MESSAGE FROM FORTUNE")
+                .execute(result -> {
+                    if (result.failed()) {
+                        context.sendError(connection, result.cause());
+                    } else {
+                        List<Fortune> fortunes = new ArrayList<>(16);
+                        RowIterator<Row> rows = result.result().iterator();
+                        while (rows.hasNext()) {
+                            Row row = rows.next();
+                            fortunes.add(new Fortune(row.getInteger(0), row.getString(1)));
+                        }
+                        context.fortunesSend(response, connection, fortunes);
+                    }
+                });
+    }
+
+    @Override
+    public void update(int queryCount, HttpResponse response, ServerHttpConnection connection,
+                       DatabaseOperationsContext context) {
+        Queue<World> worlds = new ConcurrentLinkedDeque<>();
+        SqlConnection sqlConnection = this.threadLocalConnection.get();
+        for (int i = 0; i < queryCount; i++) {
+            sqlConnection.preparedQuery("SELECT ID, RANDOMNUMBER FROM WORLD WHERE ID=$1")
+                    .execute(Tuple.of(ThreadLocalRandom.current().nextInt(1, 10001)),
+                            result -> {
+                                if (result.failed()) {
+                                    context.sendError(connection, result.cause());
+                                } else {
+                                    RowIterator<Row> rows = result.result().iterator();
+                                    if (!rows.hasNext()) {
+                                        context.sendError(connection, 404);
+                                    } else {
+                                        Row row = rows.next();
+                                        World world = new World(row.getInteger(0), ThreadLocalRandom.current().nextInt(1, 10001));
+                                        worlds.add(world);
+
+                                        if (worlds.size() == queryCount) {
+
+                                            // All worlds obtained, so run update
+                                            List<Tuple> batch = new ArrayList<>(queryCount);
+                                            for (World update : worlds) {
+                                                batch.add(Tuple.of(update.randomNumber, update.id));
+                                            }
+                                            sqlConnection.preparedQuery("UPDATE world SET randomnumber=$1 WHERE id=$2").executeBatch(batch, ar -> {
+                                                if (result.failed()) {
+                                                    context.sendError(connection, result.cause());
+                                                } else {
+
+                                                    // Updated, so send response
+                                                    context.queriesSend(response, connection, new ArrayList<>(worlds));
+                                                }
+                                            });
+                                        }
+                                    }
+                                }
+                            });
+        }
+    }
+
+}

+ 1 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/.gitignore

@@ -0,0 +1 @@
+/dependency-reduced-pom.xml

+ 35 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/pom.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.officefloor.benchmarks</groupId>
+		<artifactId>benchmarks</artifactId>
+		<version>1.0.0</version>
+	</parent>
+	<artifactId>woof_benchmark_woof</artifactId>
+	<dependencies>
+		<dependency>
+			<groupId>net.officefloor.web</groupId>
+			<artifactId>woof</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.module</groupId>
+			<artifactId>jackson-module-afterburner</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.officefloor.web</groupId>
+			<artifactId>officeweb_executive</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>net.officefloor.web</groupId>
+					<artifactId>officeweb</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+	</dependencies>
+</project>

+ 63 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/DatabaseOperations.java

@@ -0,0 +1,63 @@
+package net.officefloor.benchmark;
+
+import java.net.Socket;
+
+import net.officefloor.server.RequestHandler;
+import net.officefloor.server.http.HttpResponse;
+import net.officefloor.server.http.ServerHttpConnection;
+import net.officefloor.server.http.parse.HttpRequestParser;
+
+/**
+ * @author Daniel Sagenschneider
+ */
+public interface DatabaseOperations {
+
+	/**
+	 * Invoked on the {@link Socket} {@link Thread} to initiate {@link ThreadLocal}
+	 * setup.
+	 * 
+	 * @param requestHandler {@link RequestHandler}.
+	 */
+	void threadSetup(RequestHandler<HttpRequestParser> requestHandler);
+
+	/**
+	 * Undertakes the db.
+	 * 
+	 * @param response   {@link HttpResponse}.
+	 * @param connection {@link ServerHttpConnection}.
+	 * @param context    {@link DatabaseOperationsContext}.
+	 */
+	void db(HttpResponse response, ServerHttpConnection connection, DatabaseOperationsContext context);
+
+	/**
+	 * Undertakes the queries.
+	 * 
+	 * @param queryCount Query count.
+	 * @param response   {@link HttpResponse}.
+	 * @param connection {@link ServerHttpConnection}.
+	 * @param context    {@link DatabaseOperationsContext}.
+	 */
+	void queries(int queryCount, HttpResponse response, ServerHttpConnection connection,
+			DatabaseOperationsContext context);
+
+	/**
+	 * Undertakes the fortunes.
+	 * 
+	 * @param response   {@link HttpResponse}.
+	 * @param connection {@link ServerHttpConnection}.
+	 * @param context    {@link DatabaseOperationsContext}.
+	 */
+	void fortunes(HttpResponse response, ServerHttpConnection connection, DatabaseOperationsContext context);
+
+	/**
+	 * Undertakes the update.
+	 * 
+	 * @param queryCount Query count.
+	 * @param response   {@link HttpResponse}.
+	 * @param connection {@link ServerHttpConnection}.
+	 * @param context    {@link DatabaseOperationsContext}.
+	 */
+	void update(int queryCount, HttpResponse response, ServerHttpConnection connection,
+			DatabaseOperationsContext context);
+
+}

+ 72 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/DatabaseOperationsContext.java

@@ -0,0 +1,72 @@
+package net.officefloor.benchmark;
+
+import java.util.List;
+
+import net.officefloor.server.http.HttpResponse;
+import net.officefloor.server.http.ServerHttpConnection;
+
+/**
+ * @author Daniel Sagenschneider
+ */
+public interface DatabaseOperationsContext {
+
+    /**
+     * Obtains {@link Exception} for transient overload of resource.
+     *
+     * @return {@link Exception} for transient overload of resource.
+     */
+    Exception getTransientResourceException();
+
+    /**
+     * Sends db response.
+     *
+     * @param response   {@link HttpResponse}.
+     * @param connection {@link ServerHttpConnection}.
+     * @param world      {@link World} to send.
+     */
+    void dbSend(HttpResponse response, ServerHttpConnection connection, World world);
+
+    /**
+     * Sends queries response.
+     *
+     * @param response   {@link HttpResponse}.
+     * @param connection {@link ServerHttpConnection}.
+     * @param worlds     {@link World} instances to send.
+     */
+    void queriesSend(HttpResponse response, ServerHttpConnection connection, List<World> worlds);
+
+    /**
+     * Sends fortunes response.
+     *
+     * @param response   {@link HttpResponse}.
+     * @param connection {@link ServerHttpConnection}.
+     * @param fortune    {@link Fortune} instances to send.
+     */
+    void fortunesSend(HttpResponse response, ServerHttpConnection connection, List<Fortune> fortunes);
+
+    /**
+     * Sends update response.
+     *
+     * @param response   {@link HttpResponse}.
+     * @param connection {@link ServerHttpConnection}.
+     * @param worlds     {@link World} instances to send.
+     */
+    void updateSend(HttpResponse response, ServerHttpConnection connection, List<World> worlds);
+
+    /**
+     * Sends error.
+     *
+     * @param connection {@link ServerHttpConnection}.
+     * @param failure    Cause.
+     */
+    void sendError(ServerHttpConnection connection, Throwable failure);
+
+    /**
+     * Sends error.
+     *
+     * @param connection {@link ServerHttpConnection}.
+     * @param satus      Status.
+     */
+    void sendError(ServerHttpConnection connection, int status);
+
+}

+ 26 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/DatabaseOperationsFactory.java

@@ -0,0 +1,26 @@
+package net.officefloor.benchmark;
+
+import java.net.Socket;
+
+/**
+ * Factory for the {@link DatabaseOperations}.
+ * 
+ * @author Daniel Sagenschneider
+ */
+public interface DatabaseOperationsFactory {
+
+	/**
+	 * Creates the {@link DatabaseOperations}.
+	 * 
+	 * @param socketCount Number of server {@link Socket} instances.
+	 * @param server      Name of database server.
+	 * @param port        Port of database.
+	 * @param database    Name of database within server.
+	 * @param username    Username.
+	 * @param password    Password.
+	 * @return {@link DatabaseOperations}.
+	 */
+	DatabaseOperations createDatabaseOperations(int socketCount, String server, int port, String database,
+			String username, String password);
+
+}

+ 18 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/Fortune.java

@@ -0,0 +1,18 @@
+package net.officefloor.benchmark;
+
+/**
+ * Fortune entry.
+ * 
+ * @author Daniel Sagenschneider
+ */
+public class Fortune {
+
+	public final int id;
+
+	public final String message;
+
+	public Fortune(int id, String message) {
+		this.id = id;
+		this.message = message;
+	}
+}

+ 15 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/Message.java

@@ -0,0 +1,15 @@
+package net.officefloor.benchmark;
+
+/**
+ * Message response.
+ * 
+ * @author Daniel Sagenschneider
+ */
+public class Message {
+
+	public final String message;
+
+	public Message(String message) {
+		this.message = message;
+	}
+}

+ 481 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/RawWoof.java

@@ -0,0 +1,481 @@
+/*
+ * OfficeFloor - http://www.officefloor.net
+ * Copyright (C) 2005-2018 Daniel Sagenschneider
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package net.officefloor.benchmark;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.ClosedChannelException;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import org.apache.commons.text.StringEscapeUtils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+
+import net.officefloor.frame.api.manage.OfficeFloor;
+import net.officefloor.frame.api.manage.ProcessManager;
+import net.officefloor.frame.api.managedobject.ManagedObjectContext;
+import net.officefloor.frame.api.managedobject.ProcessSafeOperation;
+import net.officefloor.frame.api.managedobject.pool.ThreadCompletionListener;
+import net.officefloor.server.RequestHandler;
+import net.officefloor.server.SocketManager;
+import net.officefloor.server.SocketServicer;
+import net.officefloor.server.http.AbstractHttpServicerFactory;
+import net.officefloor.server.http.HttpHeaderName;
+import net.officefloor.server.http.HttpHeaderValue;
+import net.officefloor.server.http.HttpRequest;
+import net.officefloor.server.http.HttpResponse;
+import net.officefloor.server.http.HttpResponseHeaders;
+import net.officefloor.server.http.HttpServerLocation;
+import net.officefloor.server.http.HttpServerSocketManagedObjectSource;
+import net.officefloor.server.http.HttpStatus;
+import net.officefloor.server.http.ServerHttpConnection;
+import net.officefloor.server.http.impl.HttpServerLocationImpl;
+import net.officefloor.server.http.impl.ProcessAwareServerHttpConnectionManagedObject;
+import net.officefloor.server.http.parse.HttpRequestParser;
+import net.officefloor.server.http.parse.HttpRequestParser.HttpRequestParserMetaData;
+import net.officefloor.server.stream.ServerWriter;
+import net.officefloor.server.stream.impl.ThreadLocalStreamBufferPool;
+
+/**
+ * <p>
+ * {@link SocketManager} raw performance.
+ * <p>
+ * Allows determining the overhead of the {@link OfficeFloor} framework.
+ */
+public abstract class RawWoof {
+
+    /**
+     * {@link SocketManager}.
+     */
+    public static SocketManager socketManager = null;
+
+    /**
+     * {@link Logger}.
+     */
+    private static Logger logger = Logger.getLogger(RawWoof.class.getName());
+
+    /**
+     * Run application.
+     *
+     * @param args              Command line arguments.
+     * @param operationsFactory {@link DatabaseOperationsFactory}.
+     */
+    public static void run(String[] args, DatabaseOperationsFactory operationsFactory) throws Exception {
+
+        // Obtain the port from properties
+        int port = args.length > 0 ? Integer.parseInt(args[0]) : 8080;
+
+        // Ensure previous socket manager shutdown (typically from tests)
+        if (socketManager != null) {
+            socketManager.shutdown();
+        }
+
+        // Indicate details
+        String server = System.getProperty("OFFICE.net_officefloor_jdbc_DataSourceManagedObjectSource.server",
+                "tfb-database");
+        System.out.println("Starting server on port " + port + " talking to database " + server);
+
+        // Create the server location
+        HttpServerLocation serverLocation = new HttpServerLocationImpl("localhost", port, -1);
+
+        // Create a thread factory per logical CPU
+        ThreadCompletionListener[] threadCompletionListenerCapture = new ThreadCompletionListener[]{null};
+        ThreadFactory[] executionStrategy = RawWoofThreadAffinity
+                .createThreadFactories(() -> (ThreadLocalStreamBufferPool) threadCompletionListenerCapture[0]);
+        System.out.println("Using " + executionStrategy.length + " executors");
+
+        // Create the socket manager
+        socketManager = HttpServerSocketManagedObjectSource.createSocketManager(executionStrategy,
+                (threadCompletionListener) -> threadCompletionListenerCapture[0] = threadCompletionListener);
+
+        // Create the database operations
+        DatabaseOperations operations = operationsFactory.createDatabaseOperations(executionStrategy.length, server,
+                5432, "hello_world", "benchmarkdbuser", "benchmarkdbpass");
+
+        // Create raw HTTP servicing
+        RawHttpServicerFactory serviceFactory = new RawHttpServicerFactory(serverLocation, operations);
+        socketManager.bindServerSocket(serverLocation.getClusterHttpPort(), null, null, serviceFactory, serviceFactory);
+
+        // Setup Date
+        ScheduledExecutorService dateTimer = Executors.newScheduledThreadPool(1);
+        dateTimer.scheduleAtFixedRate(serviceFactory.updateDate, 0, 1, TimeUnit.SECONDS);
+
+        // Start servicing
+        Runnable[] runnables = socketManager.getRunnables();
+        for (int i = 0; i < runnables.length; i++) {
+            executionStrategy[i].newThread(runnables[i]).start();
+        }
+        Thread.sleep(1000); // allow threads to start up
+
+        // Indicate running
+        System.out.println("OfficeFloor running on port " + serverLocation.getClusterHttpPort());
+    }
+
+    /**
+     * Raw {@link AbstractHttpServicerFactory}.
+     */
+    private static class RawHttpServicerFactory extends AbstractHttpServicerFactory
+            implements DatabaseOperationsContext {
+
+        private static HttpHeaderName NAME_SERVER = new HttpHeaderName("Server");
+
+        private static HttpHeaderValue VALUE_SERVER = new HttpHeaderValue("O");
+
+        private static HttpHeaderName NAME_DATE = new HttpHeaderName("Date");
+
+        private static byte[] HELLO_WORLD = "Hello, World!".getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+        private static final HttpHeaderValue APPLICATION_JSON = new HttpHeaderValue("application/json");
+
+        private static final HttpHeaderValue TEXT_PLAIN = new HttpHeaderValue("text/plain");
+
+        private static final HttpHeaderValue TEXT_HTML = new HttpHeaderValue("text/html;charset=utf-8");
+
+        private static final String QUERIES_PATH_PREFIX = "/queries?queries=";
+
+        private static final String UPDATE_PATH_PREFIX = "/update?queries=";
+
+        private static final byte[] TEMPLATE_START = "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"
+                .getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+        private static final byte[] FORTUNE_START = "<tr><td>"
+                .getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+        private static final byte[] FORTUNE_MIDDLE = "</td><td>"
+                .getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+        private static final byte[] FORTUNE_END = "</td></tr>"
+                .getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+        private static final byte[] TEMPLATE_END = "</table></body></html>"
+                .getBytes(ServerHttpConnection.DEFAULT_HTTP_ENTITY_CHARSET);
+
+        private static final TransientResourceException THROTTLED = new TransientResourceException();
+
+        private static class TransientResourceException extends Exception {
+            private static final long serialVersionUID = 1L;
+        }
+
+        /**
+         * <code>Date</code> {@link HttpHeaderValue}.
+         */
+        private volatile HttpHeaderValue dateHttpHeader;
+
+        private final Runnable updateDate = () -> {
+            String now = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
+            RawHttpServicerFactory.this.dateHttpHeader = new HttpHeaderValue(now);
+        };
+
+        /**
+         * {@link ObjectMapper}.
+         */
+        private final ObjectMapper objectMapper = new ObjectMapper();
+
+        /**
+         * {@link ManagedObjectContext}.
+         */
+        private static ManagedObjectContext managedObjectContext = new ManagedObjectContext() {
+
+            @Override
+            public String getBoundName() {
+                return RawWoof.class.getSimpleName();
+            }
+
+            @Override
+            public Logger getLogger() {
+                return logger;
+            }
+
+            @Override
+            public <R, T extends Throwable> R run(ProcessSafeOperation<R, T> operation) throws T {
+                return operation.run();
+            }
+        };
+
+        private static int getQueryCount(String queries) {
+            try {
+                int count = Integer.parseInt(queries);
+                return (count < 1) ? 1 : (count > 500) ? 500 : count;
+            } catch (NumberFormatException ex) {
+                return 1;
+            }
+        }
+
+        /**
+         * {@link DatabaseOperations}.
+         */
+        private final DatabaseOperations databaseOperations;
+
+        /**
+         * Instantiate.
+         *
+         * @param serverLocation {@link HttpServerLocation}.
+         * @param operations     {@link DatabaseOperations}.
+         */
+        public RawHttpServicerFactory(HttpServerLocation serverLocation, DatabaseOperations operations) {
+            super(serverLocation, false, new HttpRequestParserMetaData(100, 1000, 1000000), null, null, true);
+            this.objectMapper.registerModule(new AfterburnerModule());
+            this.databaseOperations = operations;
+        }
+
+        /**
+         * Sends the {@link HttpResponse}.
+         *
+         * @param connection {@link ServerHttpConnection}.
+         * @throws IOException If fails to send.
+         */
+        protected void send(ServerHttpConnection connection) throws IOException {
+            try {
+                @SuppressWarnings("unchecked")
+                ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> rawConnection = (ProcessAwareServerHttpConnectionManagedObject<ByteBuffer>) connection;
+                rawConnection.getServiceFlowCallback().run(null);
+            } catch (IOException ex) {
+                throw ex;
+            } catch (Throwable ex) {
+                throw new IOException(ex);
+            }
+        }
+
+        /*
+         * =============== SocketServicerFactory =================
+         */
+
+        @Override
+        public SocketServicer<HttpRequestParser> createSocketServicer(
+                RequestHandler<HttpRequestParser> requestHandler) {
+
+            // Set up the thread
+            this.databaseOperations.threadSetup(requestHandler);
+
+            // Continue on to create socket servicer
+            return super.createSocketServicer(requestHandler);
+        }
+
+        /*
+         * ===================== HttpServicer ====================
+         */
+
+        @Override
+        protected ProcessManager service(ProcessAwareServerHttpConnectionManagedObject<ByteBuffer> connection)
+                throws IOException {
+
+            // Configure context
+            connection.setManagedObjectContext(managedObjectContext);
+
+            // Service the connection
+            HttpRequest request = connection.getRequest();
+            HttpResponse response = connection.getResponse();
+
+            // Provider Server and Date
+            HttpResponseHeaders headers = response.getHeaders();
+            headers.addHeader(NAME_SERVER, VALUE_SERVER);
+            headers.addHeader(NAME_DATE, this.dateHttpHeader);
+
+            // Determine request
+            String requestUri = request.getUri();
+            switch (requestUri) {
+
+                case "/plaintext":
+                    response.setContentType(TEXT_PLAIN, null);
+                    response.getEntity().write(HELLO_WORLD);
+                    this.send(connection);
+                    break;
+
+                case "/json":
+                    response.setContentType(APPLICATION_JSON, null);
+                    this.objectMapper.writeValue(response.getEntityWriter(), new Message("Hello, World!"));
+                    this.send(connection);
+                    break;
+
+                case "/db":
+                    this.databaseOperations.db(response, connection, this);
+                    break;
+
+                case "/fortunes":
+                    this.databaseOperations.fortunes(response, connection, this);
+                    break;
+
+                default:
+                    // Provide redirect
+                    if (requestUri.startsWith(QUERIES_PATH_PREFIX)) {
+                        // Obtain the number of queries
+                        String queriesCountText = requestUri.substring(QUERIES_PATH_PREFIX.length());
+                        int queryCount = getQueryCount(queriesCountText);
+
+                        // Undertake queries
+                        this.databaseOperations.queries(queryCount, response, connection, this);
+
+                    } else if (requestUri.startsWith(UPDATE_PATH_PREFIX)) {
+                        // Obtain the number of queries
+                        String queriesCountText = requestUri.substring(UPDATE_PATH_PREFIX.length());
+                        int queryCount = getQueryCount(queriesCountText);
+
+                        // Undertake update
+                        this.databaseOperations.update(queryCount, response, connection, this);
+
+                    } else {
+                        // Unknown request
+                        response.setStatus(HttpStatus.NOT_FOUND);
+                        this.send(connection);
+                    }
+                    break;
+            }
+
+            // No process management
+            return null;
+        }
+
+        /*
+         * ==================== DatabaseOperationsContext =====================
+         */
+
+        @Override
+        public Exception getTransientResourceException() {
+            return THROTTLED;
+        }
+
+        @Override
+        public void dbSend(HttpResponse response, ServerHttpConnection connection, World world) {
+            try {
+                response.setContentType(APPLICATION_JSON, null);
+                this.objectMapper.writeValue(response.getEntityWriter(), world);
+                this.send(connection);
+            } catch (CancelledKeyException | ClosedChannelException ex) {
+                // Ignore as disconnecting client
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        @Override
+        public void queriesSend(HttpResponse response, ServerHttpConnection connection, List<World> worlds) {
+            try {
+                response.setContentType(APPLICATION_JSON, null);
+                this.objectMapper.writeValue(response.getEntityWriter(), worlds);
+                this.send(connection);
+            } catch (CancelledKeyException | ClosedChannelException ex) {
+                // Ignore as disconnecting client
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        @Override
+        public void fortunesSend(HttpResponse response, ServerHttpConnection connection, List<Fortune> fortunes) {
+            try {
+                // Additional fortunes
+                fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+                Collections.sort(fortunes, (a, b) -> a.message.compareTo(b.message));
+
+                // Send response
+                response.setContentType(TEXT_HTML, null);
+                ServerWriter writer = response.getEntityWriter();
+                writer.write(TEMPLATE_START);
+                for (Fortune fortune : fortunes) {
+                    writer.write(FORTUNE_START);
+                    int id = fortune.id;
+                    writer.write(Integer.valueOf(id).toString());
+                    writer.write(FORTUNE_MIDDLE);
+                    StringEscapeUtils.ESCAPE_HTML4.translate(fortune.message, writer);
+                    writer.write(FORTUNE_END);
+                }
+                writer.write(TEMPLATE_END);
+                this.send(connection);
+            } catch (CancelledKeyException | ClosedChannelException ex) {
+                // Ignore as disconnecting client
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        @Override
+        public void updateSend(HttpResponse response, ServerHttpConnection connection, List<World> worlds) {
+            try {
+                response.setContentType(APPLICATION_JSON, null);
+                this.objectMapper.writeValue(response.getEntityWriter(), worlds);
+                this.send(connection);
+            } catch (CancelledKeyException | ClosedChannelException ex) {
+                // Ignore as disconnecting client
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        @Override
+        public void sendError(ServerHttpConnection connection, Throwable failure) {
+            try {
+
+                // Setup to send response
+                HttpResponse response = connection.getResponse();
+                response.reset();
+
+                // Determine type of error
+                if (failure instanceof TransientResourceException) {
+
+                    // Indicate overloaded
+                    response.setStatus(HttpStatus.SERVICE_UNAVAILABLE);
+
+                } else {
+                    // Provide details of failure
+                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
+                    response.setContentType(TEXT_PLAIN, null);
+                    failure.printStackTrace(new PrintWriter(response.getEntityWriter()));
+                }
+
+                // Send error response
+                this.send(connection);
+
+            } catch (CancelledKeyException | ClosedChannelException ex) {
+                // Ignore as disconnecting client
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        @Override
+        public void sendError(ServerHttpConnection connection, int status) {
+            try {
+                // Setup to send response
+                HttpResponse response = connection.getResponse();
+                response.reset();
+
+                // Flag error status
+                response.setStatus(HttpStatus.getHttpStatus(status));
+
+                // Send error response
+                this.send(connection);
+
+            } catch (IOException ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+}

+ 75 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/RawWoofThreadAffinity.java

@@ -0,0 +1,75 @@
+package net.officefloor.benchmark;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ThreadFactory;
+import java.util.function.Supplier;
+
+import net.officefloor.server.stream.impl.ThreadLocalStreamBufferPool;
+import net.officefloor.web.executive.CpuCore;
+import net.openhft.affinity.Affinity;
+
+/**
+ * Encapsulating the {@link Thread} affinity.
+ * 
+ * @author Daniel Sagenschneider
+ */
+public class RawWoofThreadAffinity {
+
+	private static final boolean IS_BENCHMARK_DEBUG = Boolean.getBoolean("officefloor.benchmark.debug");
+
+	/**
+	 * Obtains the {@link ThreadFactory} with {@link Thread} affinity.
+	 * 
+	 * @param bufferPoolSupplier Obtains the {@link ThreadLocalStreamBufferPool}.
+	 * @return {@link ThreadFactory} with {@link Thread} affinity.
+	 */
+	public static ThreadFactory[] createThreadFactories(Supplier<ThreadLocalStreamBufferPool> bufferPoolSupplier) {
+		List<ThreadFactory> threadFactories = new LinkedList<>();
+		if (IS_BENCHMARK_DEBUG) {
+			// Simple threading for debug
+			System.out.println("\n\nWARNING: using debug mode so performance will suffer\n\n");
+			for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
+				threadFactories.add((runnable) -> new Thread(() -> {
+					ThreadLocalStreamBufferPool bufferPool = bufferPoolSupplier.get();
+					try {
+						bufferPool.activeThreadLocalPooling();
+						runnable.run();
+					} finally {
+						bufferPool.threadComplete();
+					}
+				}));
+			}
+
+		} else {
+			// Provide socket per thread with thread affinity
+			for (CpuCore cpuCore : CpuCore.getCores()) {
+				for (CpuCore.LogicalCpu logicalCpu : cpuCore.getCpus()) {
+
+					// Create thread factory for logical CPU
+					ThreadFactory boundThreadFactory = (runnable) -> new Thread(() -> {
+						ThreadLocalStreamBufferPool bufferPool = bufferPoolSupplier.get();
+						try {
+							// Bind thread to logical CPU
+							Affinity.setAffinity(logicalCpu.getCpuAffinity());
+
+							// Set up for thread local buffer pooling
+							bufferPool.activeThreadLocalPooling();
+
+							// Run logic for thread
+							runnable.run();
+						} finally {
+							bufferPool.threadComplete();
+						}
+					});
+
+					// Add the thread factory
+					threadFactories.add(boundThreadFactory);
+				}
+			}
+		}
+
+		return threadFactories.toArray(new ThreadFactory[0]);
+	}
+
+}

+ 18 - 0
frameworks/Java/officefloor/src/woof_benchmark_woof/src/main/java/net/officefloor/benchmark/World.java

@@ -0,0 +1,18 @@
+package net.officefloor.benchmark;
+
+/**
+ * World response.
+ * 
+ * @author Daniel Sagenschneider
+ */
+public class World {
+
+	public final int id;
+
+	public int randomNumber;
+
+	public World(int id, int randomNumber) {
+		this.id = id;
+		this.randomNumber = randomNumber;
+	}
+}