Creating an async consumer Iteratee in Play!
The Play! Iteratee framework is very nice for reactively handling data streams. If you haven’t worked with Iteratees yet, think of them as consumers/sinks with a state. For example, in the Chat room example provided with the Play source, Iteratee.foreach imperatively consumes new messages, passing them to an actor to process.
There’s an interesting subtlety that could be missed in this example. Iteratee.foreach internally uses fold and simply discards the function’s result on each step. This may be surprising — in Scala collections, it’s the other way around, fold is implemented with foreach — but makes sense, since an Iteratee is a state machine. A side effect of this implementation is that foreach still steps through input one frame at a time, even though the result is always a Unit:
So, if you’re dealing with a high volume of data into one Iteratee, or processing a frame is not immediate, you may be surprised to find blocking behavior.
If you’re always side effecting, an easy solution is to immediately offload the request to an actor system (preferably multiple actors to concurrently process the data).
If the last bit is confusing, I encourage you to read the Iteratee.scala source. An Iteratee is in one of three states: Done, Cont, or Error. New Input is either EOF, Empty, or El(value). With EOF, we return an Iteratee that is forever in the Done state. With Empty, we’re happy to continue accepting input by returning an Iteratee in the Cont state. When we have input with El(value), we asynchronously process it, and return the an Iteratee in the Cont state.
This way, the Iteratee can immediately process more input. This is especially appropriate when your data is arriving in quick bursts, so each input can be processed independently to minimize the time between arrival and the end of processing. Obviously, this breaks FIFO and any ability for input (besides EOF) to affect the state. For most Iteratee.foreach uses that I’ve seen, however, processing input asynchronously will greatly minimize average input processing time.
We wrote this post while working on Kifi — Connecting people with knowledge. Learn more.
Originally published at eng.kifi.com on April 5, 2013.