Asynchronous I/O with libevent

Started by Jeff, October 12, 2009, 07:18:22 AM

Previous topic - Next topic

Jeff

Hey folks,



I am in the process of polishing up the interface to a wrapper module around libevent, an asynchronous I/O library for Windows, Linux, Unix, and OSX.



What is asynchronous I/O?



Threading and multiprocessing are expensive ways to take advantage of multi-core systems. They add complexity to any application that wishes to use them safely.



In most applications, the single largest bottleneck is input and output - reading/writing a file, downloading a URL, or talking back and forth with a server over a socket. In many cases, the application can do other things while waiting for a request to be sent or a response to arrive. Newlisp provides access to the common denominator of async I/O with net-select.



Most systems provide faster polling mechanisms, and libevent provides a unified API to many of them. It allows you to write your software as a series of responses to particular events - much like GUI programming with the Newlisp GUI server.



Central to this module is the Reactor context. The Reactor can be though of as a scheduler; it does the work of managing registered events and responses, and running callback functions as needed.



A (relatively) simple example



Take a simple downloader. In synchronous Newlisp:


(get-url "http://www.artfulcode.net")

Now, the user has to wait for that command to finish before anything else can happen. Using event-based methods and the Reactor module:


; The data buffer
(setf data nil)

; This is called when the socket is read to read and reads into the global buffer, data.
(define (reader socket event reactor)
 (if (zero? (net-peek socket))
  (begin
   (setf data (Reactor:get-event-storage event))
   (Reactor:kill-event event)
   (net-close socket))
  (local (buf)
   (net-receive socket buf (net-peek socket))
   (Reactor:write-event-buffer event buf))))

; Write the request to the socket. Then, remove the write event and add a read event.
(define (writer socket event reactor)
 (net-send socket "GET / HTTP/1.1rn")
 (net-send socket "Host: www.artfulcode.netrn")
 (net-send socket "Connection: closernrn")
 (Reactor:kill-event event)
 (Reactor:make-event reactor socket reader nil nil))

; Open a connection to the server
(setf socket (net-connect "www.artfulcode.net" 80))

; Create a reactor
(setf reactor (Reactor:make-reactor))

; Create an event handler that calls 'writer' when the socket is ready to be written to
(setf event (Reactor:make-event reactor socket nil writer nil))

; Run the reactor
(Reactor:run-reactor reactor)

; The reactor runs until it is told to stop in one of the callbacks, and then this code is run.
(println "Received " (length data) " bytes")


This looks a lot more complicated. However, for complex networking programs managing multiple connections, this technique is much faster, more efficient, and with some experience, just as easy to follow.



It works like this. The reactor is created. The initial event is created for the first open socket. The reactor and relevant event are passed to each callback, allowing the callbacks to kill events, create new events, and stop the reactor (effectively moving past the "run-reactor" function call).



Think about this - what if the writer callback not only scheduled a reader for the same socket, but also added a new writer event for another url in a list. Instead of killing the reactor, the reader would do something with the complete download (like write it to a file or store it in a database) and processing would continue. The urls would be downloaded concurrently, and only the work at the end would cause newlisp to do any real work.



Using this technique, I have written applications that can process hundreds of URLs every minute.



Where to get it



You can see the docs here: http://static.artfulcode.net/events/index.html">//http://static.artfulcode.net/events/index.html. You can download the module here: http://static.artfulcode.net/events/reactor.lsp">//http://static.artfulcode.net/events/reactor.lsp. You can get libevent (the required C library) here: http://www.monkey.org/~provos/libevent/">//http://www.monkey.org/~provos/libevent/



This module is new and has not been extensively tested. Please try it out and see what bugs you can find!
Jeff

=====

Old programmers don\'t die. They just parse on...



http://artfulcode.net\">Artful code

Kirill

#1
Jeff,



This is truly great news! When I have less to do at work, I'll check this thing out.



Greetings,

Kirill