瀏覽代碼

OfficeFloor asynchronous single-threaded (#6130)

* 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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Daniel 4 年之前
父節點
當前提交
582fa80a6f

+ 30 - 36
frameworks/Java/officefloor/README.md

@@ -1,33 +1,9 @@
-[![Download OfficeFloor](https://a.fsdn.com/con/app/sf-download-button)](https://sourceforge.net/projects/officefloor/files/latest/download)
+# OfficeFloor
 
-# OfficeFloor Benchmarking Test
-
-OfficeFloor provides true inversion of control.
+OfficeFloor provides true inversion of (coupling) control.
 
 > Inversion of Control = Dependency Injection + Continuation Injection + Thread Injection
 
-More information can be found at [http://officefloor.net](http://officefloor.net)
-
-
-# 503 Responses
-
- Within the performance tests (particularly the query and update tests), there are 503 responses from OfficeFloor.  These are deliberate and make OfficeFloor significantly more responsive than typical web servers.  To understand why, we need to explain the servicing change introduced by OfficeFloor.
- 
- **Typical web servers** manage load by only processing one request at a time per socket.  This means on pipelined requests over a single connection, the first request needs to be fully processed before the next request is started.  As requests then queue on the network buffer, it allows the network's natural TCP throttling to manage load.
- 
- **Web browser request profiles have changed** to an increased number of asynchronous requests pipelined over a few number of connections.  Days of old (possibly very old), web pages loaded and then did a post to reload the page again.  There would be one page submission with a few resource requests (e.g. images).  These days, single page applications are a lot more prevalent.  The nature of single page applications is to avoid page reloads and run many asynchronous requests.  This means more logic submission requests in parallel.  As browsers re-use connections to multiplex these requests to the server, the result is pipelining requests over a few number of connections.
- 
- **OfficeFloor processes pipelined requests in parallel**.  To cater to this situation and allow more responsive single page web applications, OfficeFloor processes the pipeline requests in parallel.  This means the second request in the pipeline no longer has to wait for the first one to complete.  Both the first, second, third, etc will be processed immediately by OfficeFloor.  This means better responsiveness to the single page applications, as there is no queuing of requests in the pipelines.
- 
- **Load throttling managed by 503 responses.**  As requests are no longer queued on the network buffer, there is no TCP throttling.  Requests will be accepted in parallel into OfficeFloor.  To avoid denial of service attacks, OfficeFloor has built in load throttling.  It is beyond this overview discussion to explain how this works, but basically when thread pools are overloaded they reject new jobs.  OfficeFloor captures these rejections and translates them into 503 HTTP responses (indicating temporarily unavailable).
- 
- **This improves responsiveness of single page applications**.  The problem with TCP throttling is that requests start to slow down before you get rejections.  This means your single page application ends up waiting for responses.  The wait time is noticeable by the user, as can be upwards of 10's of seconds to minutes.  This then results in the user's perception of the single page application being slow.  OfficeFloor by parallel processing everything and responding immediately with 503 responses when overloaded, reduces these long wait times.  Rather than than hanging, the single page application can deal with the 503 by calling another server or providing user friendly messages back to the user that system is under load and try back in a few moments.  This provides an overall more responsive and what we find is a better experience for the user.
- 
- However, this does mean in some of the heavier load tests, you will see errors for OfficeFloor.  But we here at OfficeFloor are proud of these errors, as they avoid hanging applications and provide better user experience under extreme loads.
-
- 
-# OfficeFloor Background
-
 OfficeFloor completes inversion of control by adding two new paradigms:
 
 * **Continuation Injection**: to inject functions to orchestrate application behaviour
@@ -36,7 +12,7 @@ OfficeFloor completes inversion of control by adding two new paradigms:
  
 In doing this, OfficeFloor is capable of running different threading models (e.g. both asynchronous single threaded and synchronous multi-threaded).  In actual fact, OfficeFloor opens up mixing the threading models within the application and even introduces ability for taking advantage of thread affinity to CPUs.
 
-This follows OfficeFloor modeling people in an office environment.  As per the paper [OfficeFloor: using office patterns to improve software design](http://doi.acm.org/10.1145/2739011.2739013) ( [free download here](http://www.officefloor.net/about.html) ), OfficeFloor follows:
+This follows OfficeFloor modeling people in an office environment:
 
 * Office being an application that makes decisions on information
 * Tasks within the Office as functions/methods (weaved together with *Continuation Injection*)
@@ -48,20 +24,22 @@ This allows OfficeFloor to better align to how business processes actually work:
 * Workers synchronously working through tasks/functions of the processes
 * Workers working asynchronously with each other
 
-In other words, people think/behave synchronously but organise asynchronously.  Hence, both thread models are in play in modelling business processes.  Furthermore, OfficeFloor makes development of asynchronous applications easier.  This is achieved by allowing the developer to avoid asynchronous coding by having synchronous functions co-ordinated asynchronously (just like workers above).
+In other words, people think/behave synchronously but organise asynchronously.  Hence, both thread models are in play in modeling business processes.  Furthermore, OfficeFloor makes development of asynchronous applications easier.  This is achieved by allowing the developer to avoid asynchronous coding by having synchronous functions co-ordinated asynchronously (just like workers above).
 
 Further to this, graphical configuration is used.  The configuration for the *officefloor* benchmark test is the following:
 
 ![Graphical Configuration](configuration.png "OfficeFloor graphical configuration")
 
+More information can be found at [http://officefloor.net](http://officefloor.net)
+
 
 # OfficeFloor Benchmark Tests
 
 OfficeFloor can use different HTTP server components:
 
-* **officefloor-netty** : incorporating Netty to service requests passing to OfficeFloor inversion of control.  Benchmark test with mature HTTP solution.
-* **officefloor-undertow** : incorporating Undertow to service requests passing to OfficeFloor inversion of control.  Benchmark test with mature HTTP solution.
-* **officefloor-raw** : default HTTP server component provided by OfficeFloor.  This allows comparing OfficeFloor's default HTTP implementation with other solutions focused on HTTP optimisation.  It is also able to process requests concurrently on the same connection for improved responsiveness.
+* **officefloor-netty** : incorporating Netty to service requests passing to OfficeFloor inversion of control.
+* **officefloor-undertow** : incorporating Undertow to service requests passing to OfficeFloor inversion of control.
+* **officefloor-raw** : default HTTP server component provided by OfficeFloor.  This allows comparing OfficeFloor's raw HTTP implementation with other solutions focused on HTTP optimisation.
 
 Having these comparisons allows developers to see the trade-offs in using different HTTP components to handle HTTP request servicing.
 
@@ -69,15 +47,31 @@ Note: OfficeFloor's web plugins are called WoOF (Web on OfficeFloor).
 
 As mentioned, OfficeFloor uses different threading models.  It does not inherit the threading model imposed by the HTTP component.  Hence, there are various threading models tested to see trade-offs:
 
-* **officefloor-micro** : typical synchronous multi-threaded model with connections retrieved from connection pool
-* **officefloor-thread_affinity** : similar to micro, except thread pools are localised onto a particular CPU.  Hence all processing for a request is done on the same CPU (allowing much better instruction cache hits).  This effectively allows running without any synchronising of threads for potentially increased throughput.
+* **officefloor-async** : asynchronous single-threaded
+* **officefloor-micro** : synchronous multi-threaded model
+* **officefloor-thread_affinity** : thread affinity of servicing each request only on one CPU
 
 While much focus is on HTTP handling, performance also is impacted by database interaction.  The above tests use raw SQL queries to provide optimised through put.  However, in "real world" applications ORMs are typically used:
 
-* **officefloor** : WoOF implementation using EntityManager
-* **officefloor-spring_data** : WoOF implementation taking advantage of Spring Data repositories.  This also demonstrates integrating Spring as a library of objects for dependency injection via OfficeFloor into functions/methods.
+* **officefloor** : uses JPA
+* **officefloor-spring_data** : uses Spring Data repositories
+
 
-**officefloor-spring_data** (with possibly **officefloor-netty** incorporated) is likely representative of application development, as provides the mature easier abstractions for the developer to work with (and less code to managed).  However, the above tests show the trade-off costs in performance for the easier development.
+# 503 Responses
+
+ Within the performance tests (particularly the query and update tests), there are 503 responses from OfficeFloor.  These are deliberate and make OfficeFloor significantly more responsive than typical web servers.  To understand why, we need to explain the servicing change introduced by OfficeFloor.
+ 
+ **Typical web servers** manage load by only processing one request at a time per socket.  This means on pipelined requests over a single connection, the first request needs to be fully processed before the next request is started.  As requests then queue on the network buffer, it allows the network's natural TCP throttling to manage load.
+ 
+ **Web browser request profiles have changed** to an increased number of asynchronous requests pipelined over a few number of connections.  Days of old (possibly very old), web pages loaded and then did a post to reload the page again.  There would be one page submission with a few resource requests (e.g. images).  These days, single page applications are a lot more prevalent.  The nature of single page applications is to avoid page reloads and run many asynchronous requests.  This means more logic submission requests in parallel.  As browsers re-use connections to multiplex these requests to the server, the result is pipelining requests over a few number of connections.
+ 
+ **OfficeFloor processes pipelined requests in parallel**.  To cater to this situation and allow more responsive single page web applications, OfficeFloor processes the pipeline requests in parallel.  This means the second request in the pipeline no longer has to wait for the first one to complete.  Both the first, second, third, etc will be processed immediately by OfficeFloor.  This means better responsiveness to the single page applications, as there is no queuing of requests in the pipelines.
+ 
+ **Load throttling managed by 503 responses.**  As requests are no longer queued on the network buffer, there is no TCP throttling.  Requests will be accepted in parallel into OfficeFloor.  To avoid denial of service attacks, OfficeFloor has built in load throttling.  It is beyond this overview discussion to explain how this works, but basically when thread pools are overloaded they reject new jobs.  OfficeFloor captures these rejections and translates them into 503 HTTP responses (indicating temporarily unavailable).
+ 
+ **This improves responsiveness of single page applications**.  The problem with TCP throttling is that requests start to slow down before you get rejections.  This means your single page application ends up waiting for responses.  The wait time is noticeable by the user, as can be upwards of 10's of seconds to minutes.  This then results in the user's perception of the single page application being slow.  OfficeFloor by parallel processing everything and responding immediately with 503 responses when overloaded, reduces these long wait times.  Rather than than hanging, the single page application can deal with the 503 by calling another server or providing user friendly messages back to the user that system is under load and try back in a few moments.  This provides an overall more responsive and what we find is a better experience for the user.
+ 
+ However, this does mean in some of the heavier load tests, you will see errors for OfficeFloor.  But we here at OfficeFloor are proud of these errors, as they avoid hanging applications and provide better user experience under extreme loads.
 
 
 # OfficeFloor real benefit

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

@@ -47,6 +47,29 @@
 				"display_name": "OfficeFloor-raw",
 				"notes": ""
 			},
+			"async": {
+				"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-async",
+				"notes": "",
+				"versus": "OfficeFloor-raw"
+			},
 			"micro": {
 				"json_url": "/json",
 				"plaintext_url": "/plaintext",

+ 10 - 0
frameworks/Java/officefloor/officefloor-async.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:slim as maven
+WORKDIR /officefloor
+COPY src src
+WORKDIR /officefloor/src/woof_benchmark_async
+RUN mvn -q clean package
+
+FROM openjdk:slim
+WORKDIR /officefloor
+COPY --from=maven /officefloor/src/woof_benchmark_async/target/woof_benchmark_async-1.0.0.jar server.jar
+CMD ["java", "-server", "-Xms2g", "-Xmx2g", "-XX:+UseNUMA", "-Dhttp.port=8080", "-Dhttp.server.name=OF", "-Dhttp.date.header=true", "-jar", "server.jar"]

+ 8 - 3
frameworks/Java/officefloor/src/pom.xml

@@ -1,6 +1,5 @@
 <?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"
+<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>
 	<groupId>net.officefloor.benchmarks</groupId>
@@ -17,6 +16,7 @@
 		<module>woof_benchmark</module>
 		<module>woof_benchmark_micro</module>
 		<module>woof_benchmark_thread_affinity</module>
+		<module>woof_benchmark_async</module>
 		<module>woof_benchmark_raw</module>
 		<module>woof_benchmark_netty</module>
 		<module>woof_benchmark_undertow</module>
@@ -39,7 +39,7 @@
 			<dependency>
 				<groupId>net.officefloor</groupId>
 				<artifactId>bom</artifactId>
-				<version>3.28.0</version>
+				<version>3.28.1</version>
 				<type>pom</type>
 				<scope>import</scope>
 			</dependency>
@@ -53,6 +53,11 @@
 				<artifactId>woof_benchmark_micro</artifactId>
 				<version>${project.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>com.github.spullara.mustache.java</groupId>
+				<artifactId>compiler</artifactId>
+				<version>0.9.7</version>
+			</dependency>
 		</dependencies>
 	</dependencyManagement>
 	<build>

+ 62 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/pom.xml

@@ -0,0 +1,62 @@
+<?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_async</artifactId>
+	<dependencies>
+		<dependency>
+			<groupId>net.officefloor.web</groupId>
+			<artifactId>woof</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.officefloor.persistence</groupId>
+			<artifactId>officer2dbc</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.r2dbc</groupId>
+			<artifactId>r2dbc-postgresql</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.r2dbc</groupId>
+			<artifactId>r2dbc-pool</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.github.spullara.mustache.java</groupId>
+			<artifactId>compiler</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<scope>provided</scope>
+		</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.OfficeFloorMain</mainClass>
+								</transformer>
+								<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
+							</transformers>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 201 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/java/net/officefloor/benchmark/Logic.java

@@ -0,0 +1,201 @@
+package net.officefloor.benchmark;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.apache.commons.text.StringEscapeUtils;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import net.officefloor.frame.api.function.AsynchronousFlow;
+import net.officefloor.r2dbc.R2dbcSource;
+import net.officefloor.server.http.HttpHeaderValue;
+import net.officefloor.server.http.HttpResponse;
+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(10000));
+	}
+
+	/**
+	 * {@link Mustache} for /fortunes.
+	 */
+	private final Mustache fortuneMustache;
+
+	public Logic() {
+
+		// Load the mustache fortunes template
+		MustacheFactory mustacheFactory = new DefaultMustacheFactory() {
+			@Override
+			public void encode(String value, Writer writer) {
+				try {
+					StringEscapeUtils.ESCAPE_HTML4.translate(value, writer);
+				} catch (IOException ex) {
+					ex.printStackTrace();
+				}
+			}
+		};
+		this.fortuneMustache = mustacheFactory.compile("fortunes.mustache");
+	}
+
+	// =========== PLAINTEXT ===================
+
+	public void plaintext(ServerHttpConnection connection) throws IOException {
+		connection.getResponse().getEntityWriter().append("Hello, World!");
+	}
+
+	// =========== JSON ===================
+
+	@Data
+	public static class Message {
+		private final String message;
+	}
+
+	public void json(ObjectResponse<Message> response) {
+		response.send(new Message("Hello, World!"));
+	}
+
+	// ============ DB ====================
+
+	@Data
+	@AllArgsConstructor
+	public class World {
+
+		private int id;
+
+		private int randomNumber;
+	}
+
+	public void db(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);
+				}), error -> async.complete(() -> {
+					throw error;
+				}));
+	}
+
+	// ========== QUERIES ==================
+
+	public void queries(@HttpQueryParameter("queries") String queries, AsynchronousFlow async, R2dbcSource source,
+			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);
+		}), error -> async.complete(() -> {
+			throw error;
+		}));
+	}
+
+	// =========== UPDATES ===================
+
+	public void update(@HttpQueryParameter("queries") String queries, AsynchronousFlow async, R2dbcSource source,
+			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);
+					}))).flatMap(world -> {
+						world.randomNumber = ThreadLocalRandom.current().nextInt(1, 10001);
+						return Flux
+								.from(connection.createStatement("UPDATE WORLD SET RANDOMNUMBER = $1 WHERE ID = $2")
+										.bind(0, world.randomNumber).bind(1, world.id).execute())
+								.flatMap(result -> Flux.from(result.getRowsUpdated()).map(updated -> world));
+					}).collectList();
+		}).subscribe(worlds -> async.complete(() -> {
+			response.send(worlds);
+		}), error -> async.complete(() -> {
+			throw error;
+		}));
+	}
+
+	// =========== FORTUNES ==================
+
+	private static final HttpHeaderValue TEXT_HTML = new HttpHeaderValue("text/html;charset=utf-8");
+
+	@Data
+	@AllArgsConstructor
+	public class Fortune {
+
+		private int id;
+
+		private String message;
+	}
+
+	public void fortunes(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(() -> {
+			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
+				HttpResponse response = httpConnection.getResponse();
+				response.setContentType(TEXT_HTML, null);
+				this.fortuneMustache.execute(response.getEntityWriter(), fortunes);
+			} catch (IOException ex) {
+				ex.printStackTrace();
+			}
+		}), error -> async.complete(() -> {
+			throw error;
+		}));
+	}
+
+	// =========== helper ===================
+
+	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;
+		}
+	}
+
+}

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

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

+ 48 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/application.woof

@@ -0,0 +1,48 @@
+<woof>
+  <http-continuations>
+    <http-continuation path="/db" secure="false" x="87" y="172">
+      <section name="Logic" input="db"/>
+    </http-continuation>
+    <http-continuation path="/fortunes" secure="false" x="78" y="213">
+      <section name="Logic" input="fortunes"/>
+    </http-continuation>
+    <http-continuation path="/json" secure="false" x="84" y="123">
+      <section name="Logic" input="json"/>
+    </http-continuation>
+    <http-continuation path="/plaintext" secure="false" x="84" y="66">
+      <section name="Logic" input="plaintext"/>
+    </http-continuation>
+    <http-continuation path="/queries" secure="false" x="82" y="270">
+      <section name="Logic" input="queries"/>
+    </http-continuation>
+    <http-continuation path="/update" secure="false" x="82" y="323">
+      <section name="Logic" input="update"/>
+    </http-continuation>
+  </http-continuations>
+  <http-inputs>
+  </http-inputs>
+  <templates>
+  </templates>
+  <sections>
+    <section name="Logic" source="net.officefloor.plugin.section.clazz.ClassSectionSource" location="net.officefloor.benchmark.Logic" x="281" y="140">
+      <input name="db" parameter-type=""/>
+      <input name="fortunes" parameter-type=""/>
+      <input name="json" parameter-type=""/>
+      <input name="plaintext" parameter-type=""/>
+      <input name="queries" parameter-type=""/>
+      <input name="update" parameter-type=""/>
+    </section>
+  </sections>
+  <procedures>
+  </procedures>
+  <securities>
+  </securities>
+  <governances>
+  </governances>
+  <resources>
+  </resources>
+  <exceptions>
+  </exceptions>
+  <starting>
+  </starting>
+</woof>

+ 8 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/datasource.properties

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

+ 1 - 0
frameworks/Java/officefloor/src/woof_benchmark_async/src/main/resources/fortunes.mustache

@@ -0,0 +1 @@
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>{{#.}}<tr><td>{{id}}</td><td>{{message}}</td></tr>{{/.}}</table></body></html>

+ 0 - 1
frameworks/Java/officefloor/src/woof_benchmark_raw/pom.xml

@@ -32,7 +32,6 @@
 		<dependency>
 			<groupId>com.github.spullara.mustache.java</groupId>
 			<artifactId>compiler</artifactId>
-			<version>0.9.7</version>
 		</dependency>
 		<dependency>
 			<groupId>org.projectlombok</groupId>