Fork me on GitHub

Thread Injection HTTP Server Tutorial

This tutorial demonstrates the ability to assign a thread pool to execute specific methods (i.e. thread injection). OfficeFloor refers to a thread pool as a Team.

The example used for this tutorial is an encrypter for a letter. A database table stores the encryption by mapping the letter to an alternate letter. Once the letter encryption is looked up in the database it is cached to provide quicker subsequent look ups for that letter. The example's web page is as follows and has been kept simple to focus on thread injection.

TeamHttpServer screen shot.

The example displays the names of two threads to show the different thread pools (Teams) servicing the request.

  • thread to obtain cached letter encryption
  • thread (pool) to look up letter encryption within the database

Download Tutorial Source

Web Server Efficiency and Responsiveness

The thread per request architecture of most web servers will typically have a single thread pool to service all requests. Database I/O is typically blocking. Under heavy loads all threads can end up blocked waiting on the database. As the web server has no free threads, it queues further requests degrading the responsiveness of the web application.

Most requests to a web server are for static cached content. Having these requests wait behind longer database requests means non-optimal use of the web server. The blocked threads leaves the web server's CPU idle. Having an additional thread would allow the web server to continue servicing the static cached content requests. This is especially relevant as the company's home page is usually static cached content and users have a low tolerance for slow web site responsiveness. Also increasing the number of threads for cached content can cause lock contention and potential starvation resulting in degraded responsiveness.

Serving cached content is best by a single thread while database content is best by a pool of threads. These two types of request are typically handled by most web servers with a single thread pool. However increasing the efficiency and responsiveness of a web server is not achieved by increasing the number of threads but by increasing the number of thread pools.

The example for this tutorial shows how OfficeFloor enables increasing the number of thread pools for efficient servicing of cached and database requests.

Configuration

The following is the application.woof configuration for the example application.

application.woof configuration.

The DataSource to the database is configured as follows.

<objects>

	<managed-object source="net.officefloor.plugin.jdbc.datasource.DataSourceManagedObjectSource" type="javax.sql.DataSource">
		<property-file path="datasource.properties" />
	</managed-object>

</objects>

With the following properties.

data.source.class.name=org.hsqldb.jdbc.jdbcDataSource
database=jdbc:hsqldb:mem:exampleDb
user=sa

See the other tutorials for explanation of the above configuration.

Thread Injection Configuration

The thread injection configuration is contained in the application.teams file at the root of the class path. For the example application it is as follows.

<teams>

	<team source="net.officefloor.frame.impl.spi.team.LeaderFollowerTeamSource" type="javax.sql.DataSource">
	    <property name="size" value="10" />
	</team>

</teams>

The leader follower team is a TeamSource implementation that implements the leader follower thread pool pattern to reduce lock contention.

WoOF uses the method's dependencies to determine the type of functionality being undertaken by the method. The method's dependencies give an indicator of what the method is likely to be doing. In this case if the method requires a DataSource it is very likely that it will be doing blocking database I/O calls. This means of classifying methods allows OfficeFloor to auto-magically use the appropriate thread pool to execute the method. It is what OfficeFloor considers thread injection.

All other methods are executed by the default Team. Running stand-alone this would be the socket listener thread.

Adding thread pools is optional and therefore the inclusion of the application.teams file is optional. It is anticipated that threading will be configured closely to the dependencies available within an environment. In many cases this will be handled by the environment deployment. The file however is supported for extending WoOF web applications by customising the thread pools.

Code

The following is the content of the template.

<html>
	<body>
		<form action="#{encrypt}">
			Letter: <input name="letter" value="${letter}" type="text" maxlength="1" />
			<input type="submit" value="Code" />
		</form>
		<p>Encrypted code: ${code}</p>
		<br />
		<!-- {ThreadNames} -->
		<p>Cache thread: ${cacheThreadName}</p>
		<p>Database I/O thread: ${databaseThreadName}</p>
	</body>
</html>

The following provides the values for the ${property} entries from the template logic.

@HttpSessionStateful
public class Template implements Serializable {

	private Map<Character, LetterEncryption> cache = new HashMap<Character, LetterEncryption>();

	private LetterEncryption displayCode;
	private String cacheThreadName;
	private String databaseThreadName;

	public LetterEncryption getTemplate() {
		return (this.displayCode == null ? new LetterEncryption(' ', ' ')
				: this.displayCode);
	}

	public Template getThreadNames() {
		return this;
	}

	public String getCacheThreadName() {
		return this.cacheThreadName;
	}

	public String getDatabaseThreadName() {
		return this.databaseThreadName;
	}

Along with providing the values the class is also annotated so that it is stored within the HTTP session. This allows the cache field to act as a cache across requests. See the other tutorials for further details.

The example application first tries the cache for the encrypted code.

	@FlowInterface
	public static interface PageFlows {
		void retrieveFromDatabase(char letter);
	}

	@NextTask("setDisplayCode")
	public LetterEncryption encrypt(EncryptLetter request, PageFlows flows) {

		// Specify thread name (clearing database thread)
		this.cacheThreadName = Thread.currentThread().getName();
		this.databaseThreadName = "[cached]";

		// Obtain from cache
		char letter = request.getLetter();
		LetterEncryption code = this.cache.get(new Character(letter));
		if (code != null) {
			return code;
		}

		// Not in cache so retrieve from database
		flows.retrieveFromDatabase(letter);
		return null; // for compiler
	}

	public void setDisplayCode(@Parameter LetterEncryption encryption) {
		this.displayCode = encryption;
	}

Should the encrypted code be found in the cache it is passed as a parameter to the setter method. The setter method keeps reference to the encrypted code for rendering the web page response. See the other tutorials for explanation of this WoOF functionality.

On not finding the encrypted code in the cache the above method triggers for it to be retrieved from the database. The following method retrieves the encrypted code from the database. The returned encrypted code is passed to the setter method for rendering to the web page.

	@NextTask("setDisplayCode")
	public LetterEncryption retrieveFromDatabase(@Parameter char letter,
			DataSource dataSource) throws SQLException {

		// Specify thread name
		this.databaseThreadName = Thread.currentThread().getName();

		// Retrieve from database and cache
		Connection connection = dataSource.getConnection();
		try {
			PreparedStatement statement = connection
					.prepareStatement("SELECT CODE FROM LETTER_CODE WHERE LETTER = ?");
			statement.setString(1, String.valueOf(letter));
			ResultSet resultSet = statement.executeQuery();
			resultSet.next();
			String code = resultSet.getString("CODE");
			LetterEncryption letterCode = new LetterEncryption(letter,
					code.charAt(0));

			// Cache
			this.cache.put(new Character(letter), letterCode);

			return letterCode;
		} finally {
			connection.close();
		}
	}

As the method has a DataSource dependency it is executed by the leader follower team. This is reflected by the web page response showing different threads executing the cache and database methods.

Though this is a simple example it does highlight that under heavy load that cached letter encryptions can still be serviced even if all database (leader follower) threads are blocked waiting on the database.

Remaining code

For completeness of the tutorial the remaining code for the example application is included below.

Database Setup

public class Setup {

	public void setupDatabase(DataSource dataSource) throws SQLException {

		Connection connection = dataSource.getConnection();
		try {
			connection
					.createStatement()
					.execute(
							"CREATE TABLE LETTER_CODE ( LETTER CHAR(1) PRIMARY KEY, CODE CHAR(1) )");
			
			PreparedStatement statement = connection
					.prepareStatement("INSERT INTO LETTER_CODE ( LETTER, CODE ) VALUES ( ?, ? )");
			for (char letter = ' '; letter <= 'z'; letter++) {
				char code = (char) ('z' - letter + ' '); // simple reverse order
				statement.setString(1, String.valueOf(letter));
				statement.setString(2, String.valueOf(code));
				statement.execute();
			}
		} finally {
			connection.close();
		}
	}

}

EncryptLetter

@HttpParameters
public class EncryptLetter implements Serializable {

	private char letter;

	public void setLetter(String letter) {
		this.letter = (letter.length() == 0 ? ' ' : letter.charAt(0));
	}

	public char getLetter() {
		return this.letter;
	}

}

LetterEncryption

public class LetterEncryption implements Serializable {

	private char letter;

	private char code;

	public LetterEncryption(char letter, char code) {
		this.letter = letter;
		this.code = code;
	}

	public char getLetter() {
		return this.letter;
	}

	public char getCode() {
		return this.code;
	}
}

Unit Test

The following unit test makes requests to encrypt a letter.

	public void testRetrieveEncryptions() throws Exception {

		// Request page to allow time for database setup
		this.doRequest("http://localhost:7878/example.woof");

		// Retrieving from database
		this.doRequest("http://localhost:7878/example-encrypt.woof?letter=A");

		// Looking up within cache
		this.doRequest("http://localhost:7878/example-encrypt.woof?letter=A");
	}

	private void doRequest(String url) throws Exception {
		HttpResponse response = this.client.execute(new HttpGet(url));
		response.getEntity().writeTo(System.out);
		assertEquals("Request should be successful", 200, response
				.getStatusLine().getStatusCode());
	}

As the same letter is requested, the

  • first request retrieves the encrypted code from the database (and caches it)
  • second request retrieves it from the cache

Next

Please see the other tutorials for further features.