We have lost contact to the maintainers of the following projects on common-lisp.net:
- fret
- ganelon
- innen
- erlisp
We have lost contact to the maintainers of the following projects on common-lisp.net:
Parallel programming is hard, and as CPUs are getting ever-faster, I usually tend to find optimizing a single thread of control to be less risky than dealing with threads, locks and synchronized data structures. Recently, though, I had to deal with a reporting function which I started from a Hunchentoot handler and that was running long enough to time out the client. I needed to somehow put running the function into the background, and as I heard good things about the relatively new lparallel library, I thought I could give it a try.
lparallel implements an abstraction layer for parallel programming in Common Lisp. In addition to relatively low-level concepts like tasks and channels, it implements mid-level promises and futures as well as high-level parallel mapping, binding and reducing functionality. Futures looked like a suitable mechanism to solve my problem at hand.
The non-parallel version of my HTTP request handler looked something like this:
(hunchentoot:define-easy-handler (something-that-takes-long :uri "/sttl") (parameter) (if (eql (hunchentoot:request-method*) :post) (compute-and-display-response-page parameter) (show-job-parameter-form)))The compute-and-display-response-page function does what the name suggests. As usual with Hunchentoot request handlers, it returns the page to display to the user. Likewise, the show-job-parameter-form returns a HTML form that collects the parameter. Now, compute-and-display-response-page potentially takes long, so instead of just delaying the response to the HTTP request until the function returns, I want to have it execute in the background and respond to the request with a "job has been started" message. The client is then supposed to poll in regular intervals. Once the background job completes, the page that the function has generated is returned to the client.
Using a future, I came up with something like this:
(hunchentoot:define-easy-handler (something-that-takes-long :uri "/sttl") (parameter) ;; Get or start Hunchentoot session context (hunchentoot:start-session) (let ((job-running (hunchentoot:session-value :job))) (cond ;; Previously started job has finished ((and job-running (lparallel:fulfilledp job-running)) (hunchentoot:delete-session-value :job) (lparallel:force job-running)) ;; Previously started job still running (job-running "Previous job still running") ;; Start new job ((eq (hunchentoot:request-method*) :post) (setf (hunchentoot:session-value :job) (lparallel:future (compute-and-display-response-page parameter))) "The job has been started") ;; Display job parameter form (t (show-job-parameter-form)))))Hunchentoot's session mechanism is used to make the future accessible to subsequent requests. The future is placed in a session value; the completion of the background calculation is determined by calling lparallel:fulfilledp. Once the computation is finished, the return value of the compute-and-display-response-page is determined using lparallel:force.
This is mostly it, and I find parallel programming in this case easy to understand and reason about. Some additional things are worth mentioning:
lparallel has more to offer of course, and I have only used a very small part of it. So far, I have found it to be very well thought out, documented appropriately and well maintained. If you need parallel programming or even just easy background execution in your Common Lisp programs, I recommend looking at it.