Permalink
Browse files

Grails 2.3 async features documentation

  • Loading branch information...
graemerocher committed Mar 15, 2013
1 parent 065c7e4 commit 88abe7cf9aae587a40a364a26e58eff029021b46
View
@@ -0,0 +1,5 @@
With modern hardware featuring multiple cores, many programming languages have been adding asynchronous, parallel programming APIs, Groovy being no exception.
The excellent [GPars|http://gpars.codehaus.org] project features a whole range of different APIs for asynchronous programming techniques including actors, promises, STM and data flow concurrency.
Added Grails 2.3, the Async features of Grails aim to simplify concurrent programming within the framework and include the concept of Promises and a unified event model.
@@ -0,0 +1,108 @@
Since Grails 2.3, GORM features an asynchronous programming model that works across all supported datastores (Hibernate, MongoDB etc.).
h3. Async Namespace
The Asynchronous GORM API is available on every domain class via the @async@ namespace.
For example, the following code listing reads 3 objects from the database asynchronously:
{code}
import static grails.async.Promises.*
def p1 = Person.async.get(1L)
def p2 = Person.async.get(2L)
def p3 = Person.async.get(3L)
def results = waitAll(p1, p2, p3)
{code}
Using the @async@ namespace, all the regular GORM methods are available (even dynamic finders), but instead of executing synchronously, the query is run in the background and a @Promise@ instance is returned.
The following code listing shows a few common examples of GORM queries executed asynchronously:
{code}
import static grails.async.Promises.*
Person.async.list().onComplete { List results ->
println "Got people = ${results}"
}
def p = Person.async.getAll(1L, 2L, 3L)
List results = p.get()
def p1 = Person.async.findByFirstName("Homer")
def p2 = Person.async.findByFirstName("Bart")
def p3 = Person.async.findByFirstName("Barney")
results = waitAll(p1, p2, p3)
{code}
h3. Async and the Session
When using GORM async each promise is executed in a different thread. Since the Hibernate session is not concurrency safe, a new session is bound per thread.
This is an important consideration when using GORM async (particularily with Hibernate as the persistence engine). The objects returned from asynchronous queries will be detached entities.
This means you cannot save objects returned from asynchronous queries without first merging them back into session. For example the following will not work:
{code}
def promise = Person.async.findByFirstName("Homer")
def person = promise.get()
person.firstName = "Bart"
person.save()
{code}
Instead you need to merge the object with the session bound to the calling thread. The above code needs to be written as:
{code}
def promise = Person.async.findByFirstName("Homer")
def person = promise.get()
person.merge()
person.firstName = "Bart"
{code}
Note that @merge()@ is called first because it may refresh the object from the cache or database, which would result in the change being lost. In general it is not recommended to read and write objects in different threads and you should avoid this technique unless absolutely necessariy.
Finally, another issue with detached objects is that association lazy loading *will not* work and you will encounter @LazyInitializationException@ errors if you do so. If you plan to access the associated objects of those returned from asynchronous queries you should use eager queries (which is recommended anyway to avoid N+1 problems).
h3. Multiple Asynchronous GORM calls
As discussed in the previous section you should avoid reading and writing objects in different threads as merging tends to be inefficient.
However, if you wish to do more complex GORM work asynchronously then the GORM async namespace provides a @task@ method that makes this possible. For example:
{code}
def promise = Person.async.task {
withTransaction {
def person = findByFirstName("Homer")
person.firstName = "Bart"
person.save(flush:true)
}
}
Person updatedPerson = promise.get()
{code}
Note that the GORM @task@ method differs from the static @Promises.task@ method in that it deals with binding a new session to the asynchronous thread for you. If you do not use the GORM version and do asynchronous work with GORM then you need to do this manually. Example:
{code}
import static grails.async.Promises.*
def promise = task {
Person.withNewSession {
// your logic here
}
}
{code}
h3. Async DetachedCriteria
The @DetachedCriteria@ class also supports the @async@ namespace. For example you can do the following:
{code}
DetachedCriteria query = Person.where {
lastName == "Simpson"
}
def promise = query.async.list()
{code}
@@ -0,0 +1,78 @@
If you are deploying to a Servlet 3.0 container such as Tomcat 7 and above then it is possible to deal with responses asynchronously.
In general for controller actions that execute quickly there is little benefit in handling requests asynchronously. However, for long running controller actions it is extremely beneficial.
The reason being that with an asynchronous / non-blocking response, the one thread == one request == one response relationship is broken. The container can keep a client response open and active, and at the same time return the thread back to the container to deal with another request, improving scalability.
For example, if you have 70 available container threads and an action takes a minute to complete, if the actions are not executed in a non-blocking fashion the likelihood of all 70 threads being occupied and the container not being able to respond is quite high and you should consider asynchronous request processing.
Since Grails 2.3, Grails features a simplified API for creating asynchronous responses built on the @Promise@ mechism discussed previously.
The implementation is based on Servlet 3.0 async so to enable the async features you need to set your servlet target version to 3.0 in BuildConfig.groovy:
{code}
grails.servlet.version = "3.0"
{code}
h4. Async Models
A typical activity in a Grails controller is to produce a model (a map of key/value pairs) that can be rendered by a view.
If the model takes a while to produce then the server could arrive at a blocking state, impacting scalability. You tell Grails to build the model asynchronously by returning a @grails.async.PromiseMap@ via the @Promises.tasks@ method:
{code}
import static grails.async.Promises.*
...
def index() {
tasks books: Book.async.list(),
totalBooks: Book.async.count(),
otherValue: {
// do hard work
}
}
{code}
Grails will handle the response asynchronously, waiting for the promises to complete before rendering the view. The equivalent synchronous action of the above is:
{code}
def index() {
def otherValue = ...
[ books: Book.list() ,
totalBooks: Book.count(),
otherValue: otherValue ]
}
{code}
You can even render different view by passing the @PromiseMap@ to the @model@ attribute of the @render@ method:
{code}
import static grails.async.Promises.*
...
def index() {
render view:"myView", model: tasks( one:{ 2 * 2 },
two:{ 3 * 3 } )
}
{code}
h4. Async Response Rendering
You can also write to the response asynchronously using promises in Grails 2.3 and above:
{code}
import static grails.async.Promises.*
class StockController {
def stock(String ticker) {
task {
ticker = ticker ?: 'GOOG'
def url = new URL("http://download.finance.yahoo.com/d/quotes.csv?s=${ticker}&f=nsl1op&e=.csv")
Double price = url.text.split(',')[-1] as Double
render "ticker: $ticker, price: $price"
}
}
}
{code}
The above example using Yahoo Finance to query stock prices, executing asynchronously and only rendering the response once the result has been obtained. This is done by returning a @Promise@ instance from the controller action.
If the Yahoo URL is unresponsive the original request thread will not be blocked and the container will not become unresponsive.
@@ -1,16 +1,6 @@
Grails support asynchronous request processing as provided by the Servlet 3.0 specification. To enable the async features you need to set your servlet target version to 3.0 in BuildConfig.groovy:
In addition to the higher level async features discussed earlier in the section, you can access the raw Servlet 3.0 asynchronous API from a Grails application.
{code}
grails.servlet.version = "3.0"
{code}
With that done ensure you do a clean re-compile as some async features are enabled at compile time.
{note}
With a Servlet target version of 3.0 you can only deploy on Servlet 3.0 containers such as Tomcat 7 and above.
{note}
h4. Asynchronous Rendering
h4. Servlet 3.0 Asynchronous Rendering
You can render content (templates, binary data etc.) in an asynchronous manner by calling the @startAsync@ method which returns an instance of the Servlet 3.0 @AsyncContext@. Once you have a reference to the @AsyncContext@ you can use Grails' regular render method to render content:
@@ -0,0 +1 @@
TODO: Stephane?
Oops, something went wrong.

0 comments on commit 88abe7c

Please sign in to comment.