Saturday, September 12, 2009

Erlang-Style Programming in PLT

Similar to Erlang, PLT Scheme also offers microthreads. While the PLT's concurrency implementation might not yet be as capable as Erlang's (currently PLT is not yet multicore enabled), we can still employ similar development style.

The base of PLT's thread primitives are documented in the PLT reference documentation. Specifically, to spawn a thread, we just need to call (thread <procedure>). To send the thread a message, use (thread-send <thd> <msg>). The target thread should call (thread-receive) to retrieve the message. This basic pattern works well with two threads communicating with each other. Once more than two threads are involved, we run into issues with just (thread-receive) since we have no way to verify which thread sends which message, let along sending back the right response to the right receipient. We'll need something more.

Introducing bzlib/thread - implementing the erlang selective receive pattern in PLT Scheme. The code is released under LGPL.

In Erlang, the receive is pattern match enabled, and we want something similar in PLT scheme. bzlib/thread provides the pattern match enabled receive for PLT Scheme, with the syntax receive/match:

(require (planet blib/thread)) ;; load the package.
;; default syntax - with pattern match
(receive/match
(match-pattern exp ...) ...)

;; timer syntax - just the timer.
(receive/match
(after time exp ...))

;; extended syntax - combine the pattern match with timer.
(receive/match
(match-pattern exp ...) ...
(after time exp ...))

The match-pattern has the exact same syntax as scheme/match, since scheme/match provides the underlying matching capability. You can also specify an after clause so you can specify the number of seconds (does not have to be integer) elapsed for a timer-based event to trigger even without thread messages.

Beyond Erlang's Receive Capability

While PLT Scheme's concurrency capability is not yet as capable as Erlang, its language facility has more to offer than Erlang's. By default PLT Scheme provides a powerful synchronization framework that includes many different types of events (the after clause is built on top of the alarm event), and it would be a shame if we cannot take advantage of all those event capabilities within our receive/match, hence receive/match is enhanced with a sync clause:

;; with the sync clause - it is also pattern-match enabled...
(receive/match
(pattern-match exp ...) ...
(after time exp ...)
(sync (pattern exp ...) ...))

Hence you can pass in a list of custom events so the syntax will handle all of the synchronizations within one clause. The sync clause also have pattern matching, so you can use it to match against the specific events that was triggered and dispatch to the correct clause branch.

The receive/match form the basis of multi-threads communications.

Communication Bewteen Multiple Threads

As stated earlier, the challenge with the bare thread-send and thread-receive is that you have to handle your own message dispatching if there are multiple threads trying to communicate with each other. While receive/match provides the additional pattern matching capability, it only simplifies your effort to coordinate all of the threads, but you still have to devise the scheme in which you can identify the sending thread (so you can send back an appropriate response). To do so, we need to codify the structure of the message so it includes information about the sender. The simplest way is to add the sending thread into the message as follows:

(thread-send thd (list (current-thread) args))

Then you just need to ensure your receive/match matches the signature:

(receive/match ((list (? thread? thd) args) (do-whatever) ...) ...)
bzlib/thread codifies the above signature in thread-call, so all you have to do is:

(thread-call thd args)
;; or with a timeout
(thread-call thd args timeout)

and the above message signature will be sent.

Once you've received the message via receive/match and extracted your args for processing, you can then use thread-reply to send a message back to the sender:

(thread-reply sender result)
;; or you can reply on behalf of another thread
(thread-reply sender result thread-on-behalf-of)


Handling Exceptions and Respond to Sender

What if the processing raised an exception and you need to send the exception back to the sender? To avoid confusion between a regular reply (which holds legit values) and an exception, you should use send-exn-to to return the exception, which does the following:

(thread-send thd (cons exn thread) #f) ;; #f means no error is raised if the receiving thread is dead.

Then you just match for the exception pattern to check whether an exception was raised:

(receive/match ((cons (? exn? e) (? thread? sender)) (do-something...)) ...)


Casting Messages

The above thread-call, thread-reply, and send-exn-to codifies the communication signatures between threads so the correct reply can be sent back to the originator, but what if you do not need to respond back to the originator? In that case you have a "cast" pattern, and bzlib/thread provides thread-cast and thread-cast* for you.

(thread-cast thd arg)
(thread-cast* thd arg arg1 ...) ;; equals (thread-cast thd (list arg arg1 ...))

They are very similar to the bare thread-send, except they are written with the kill-safe pattern, so they'll first attempt to wake the receiving thread if it was suspended, and then send over the arguments. Use thread-cast or thread-cast* if you do not need to get a response back.

Applications and Getting Closer to OTP

Erlang is famous for its nine-nines uptime, and their OTP modules have a lot to do with it. It takes a lot of effort to implement OTP and get all of the bugs out, so OTP is not coming to PLT soon. But app which is part of bzlib/thread is the first step toward constructing OTP for PLT.

App basically provides a simple structure over the receiving thread, so you have an structure that you can pass around and manipulate. The function make-application tries to simplify the creation of an application:

(make-application call cast init-state)

You just need to pass the call function (run when triggered via thread-call) and the cast function (run when triggered via thread-cast), as well as the init-state, which are the values that the application will hold internally between the thread calls or casts.

The call function should have the following signature:

(sender-thread passed-in-args app-state . -> . (cons/c result app-state))

The returned app-state do not have to be the same as the passed in app-state, but it needs to be compatible to the function for the next call.

The cast function should have the following signature:

(passed-in-args app-state . -> . (cons/c result app-state))

The result will be disgarded since there isn't a response back to the sender.

app-call and app-cast are provided as wrappers over thread-call and thread-cast. Unlike thread-call and thread-cast, app-call and app-cast takes variable parameter lists.

(app-call app cmd #:timeout (number? +inf.0) arg1 ...)
(app-cast app cmd arg1 ...)

cmd is a symbol that you can use to dispatch to the correct function within your app, assuming your app provides multiple function as its APIs.

That's it for now - have fun programming in erlang style in PLT Scheme.

2 comments:

  1. Cool post, I'm a web developer currently learning Scheme and have had some experience with Erlang (switched from Common Lisp, it was too messy for me).

    Great post for me! Thank you :)

    ReplyDelete
  2. @Ixmatus - thanks. I plan on adding more erlang-style capabilities to PLT Scheme. Stay tuned.

    ReplyDelete