Put on Your Monocle and Do Some Async Programming

DZone 's Guide to

Put on Your Monocle and Do Some Async Programming

· Performance Zone ·
Free Resource
Greg and Steven Hazel from Sauce Labs have recently built what they call, "An async programming framework with a blocking look-alike syntax".  This framework, named Monocle, is focused on being portable between event-driven I/O frameworks.  Currently, Monocle supports the Twisted and Tornado frameworks.

For those who haven't heard of Sauce Labs, they were co-founded by John Huggins, the creator of Selenium.  Sauce Labs' free and commercial tools build on top of the core Selenium testing framework.

The emergence of Monocle could indicate that Sauce Labs is taking a more focused look at event-driven code and its role in concurrent web performance.  Event-driven code is efficient and intuitive, but sometimes procedures are are split up and code is expanded in a not-so-good way.  

Anytime you do I/O, you have to register a new handler and then return to the event loop so that you can let other things happen while the I/O finishes.  Hazel and Hazel said, "It would be nice if we had some way to tell the event loop to call back into the middle of our function, so we could just continue where we left off."  In Python, they found a mechanism that can do just that.  

The mechanisms are called generators, and Monocle uses this Python syntax supported by Python 2.5 and up.  The generators in Monocle straighten out event-based code such as this blocking code (expanded into four functions):
def get_cmd(conn):
conn.read_until("\n", callback=handle_cmd)

def handle_cmd(conn, cmd):
if cmd.type == "get-address":
# keep track of the conn so we can write the response back!
def callback(result):
handle_user_query_result(conn, result)
db.query(cmd.username, callback)
conn.write("unknown command")

def handle_user_query_result(conn, user):
Which is transformed into code like this, via Monocle:
def do_cmd(conn):
cmd = yield conn.read_until("\n")
if cmd.type == "get-address":
user = yield db.query(cmd.username)
yield conn.write(user.address)
yield conn.write("unknown command")
Sauce Labs believes Monocle will make code easier to work with than multi-threaded code, thanks to its "cooperative concurrency" approach.

Here's a basic Monocle program running two concurrent processes called "o-routines" using Tornado's event loop.  One process is an HTTP server, and the other makes an HTTP request:
import monocle

from monocle.stack import eventloop
from monocle.stack.network import add_service
from monocle.stack.network.http import HttpClient, HttpHeaders, HttpServer, http_respond

def hello_http(req):
content = "Hello, World!"
headers = HttpHeaders()
headers['Content-Length'] = len(content)
headers['Content-Type'] = 'text/plain'
yield http_respond(req, 200, headers, content)

def request():
client = HttpClient()
resp = yield client.request('')
print resp.code, resp.body

add_service(HttpServer(hello_http, 8088))
And instead of @monocle.o, you can use the don the monocle ( @_o) as a shortcut:
from monocle import _o

def request():
client = HttpClient()
resp = yield client.request('')
print resp.code, resp.body
Hazel and Hazel admit, "this violates Python's convention that underscores indicate variables for internal use. But rules are for breaking. Live a little."

Check out Monocle on GitHub.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}