[e-lang] [Fwd: Re: Proposal: E / Erlang integration]

David Hopwood david.nospam.hopwood at blueyonder.co.uk
Thu Jun 8 22:17:36 EDT 2006


Ulf Wiger wrote:
> Mark S. Miller wrote:
>> Continuing with section 24.2 of my thesis:
>>
>>>Although Erlang was inspired by Actors, a common programming pattern
>>>in Erlang is for a process to block waiting for a reply to a
>>>previous request. While blocked, the process is not responsive to
>>>other requests. This is like the "message-oriented systems" of Lauer
>>>and Needham [LN79] rather than the non-blocking event loops of E.
>>
>>>[LN79] Hugh C. Lauer and Roger M. Needham. On the Duality of
>>>Operating System Structures. In Proc. 2nd International Symposium on
>>>Operating Systems, pages 3--19. IRIA, October 1979. ACM Operating
>>>System Review.
>>
>> However, if Erlang systems don't seem to be vulnerable to deadlock in
>> practice, the Erlang community may not find the above difference
>> compelling.
> 
> You could read and perhaps comment on my presentation at EUC 05:
> 
> Structured Network Programming
> (http://www.erlang.se/euc/05/1500Wiger.ppt), where I take a basic
> telephony problem ("complex concurrency") and implement it both using
> selective receive and in an event-driven, non-blocking fashion.

It may be instructive to show how this example would be handled in E.
The executive summary is that E can implement it with the "non-blocking
restriction" just as straightforwardly as the blocking code, even though
E does not support selective receive:


# Like the Erlang example, we don't handle errors.

# The hardware, which can be controlled asynchronously.
def lim {
    to startTone(tone :String) { ... }
    to stopTone() { ... }
    to startRinging() { ... }
}

def makePOTS() {
    return def pots {
        # Declare the states, to avoid forward references...
        def waiting, idle, getting_first_digit, ringing_B_side, calling_B,
            ringing_A_side, speech

        # The peer we are connecting to.
        var B

        # The number being dialed.
        var number :String := ""

        # This is just a way to make a process that has different behaviours
        # over time look like a single object.
        var state := idle
        match [verb, args] { E.call(state, verb, args) }

        # Now define the states...

        # waiting is a dummy state used while we are waiting for the hardware.
        # (In a real example there would have to be a way of getting out of this
        # state if the hardware fails, but the Erlang code doesn't deal with that
        # either.)
        bind waiting {}

        bind idle {
            to offhook() {
                state := waiting
                when (lim <- startTone("dial")) -> {
                    state := getting_first_digit
                }
            }
            to digit() { # ignore
            }
            to requestConnection(peer) {
                # This is a literal interpretation of the Erlang code, but it is
                # not what should be done in E for security reasons -- the peer
                # should only get a facet, not full access to the pots object.
                peer <- accept(pots)
                state := waiting
                B := peer
                when (lim <- startRinging()) -> {
                    state := ringing_B_side
                }
            }
            # we don't need to match unexpected messages here, but we could.
        }

        bind getting_first_digit {
            to onhook() {
                state := waiting
                when (lim <- stopTone()) -> {
                    state := idle
                }
            }
            to digit(d) {
                state := waiting
                when (lim <- stopTone()) -> {
                    # the Erlang code seemed to be missing some context, but
                    # this is probably about right...
                    number := analyse(number, d, validSequences())
                    state := getting_number
                }
            }
            to requestConnection(peer) {
                # same security comment as above
                peer <- reject(pots)
            }
        }

        bind calling_B {
            to onhook() {
                state := idle
            }
            to digit(d) { # ignore
            }
            to accept(peer) {
                state := waiting
                B := peer
                when (lim <- startTone("ring")) -> {
                    state := ringing_A_side
                }
            }
            to reject(peer) {
                state := waiting
                peer := null
                when (lim <- startTone("busy")) -> {
                    state := wait_on_hook
                }
            }
            to requestConnection(peer) {
                # same security comment as above
                peer <- reject(pots)
            }
        }

        ... # more states, also not shown in Erlang code
    }
}

The differences from the Erlang code are mainly due to a different encoding
of the state machine (using an explicit state rather than tail recursion), and
are not fundamental. However, although this code has the same structure as the
blocking Erlang example, it never blocks, and it cannot be subject to a low-level
deadlock.

> One of the things to note is that while it is possible to accomplish low-level
> deadlocks, the application-level concurrency issues are the really hairy
> ones. You may well accomplish an application-level deadlock even with a
> fully non-blocking, event-driven design.

True -- as I've written it above, the 'waiting' state can cause an
application-level deadlock if the hardware never responds to a request. But
note that I was effectively forced to make this state explicit.

> In Erlang, programmers tend to learn pretty quickly to insert timeout
> guards on receive statements most of the time. If one uses gen_servers,
> there's a built-in 5-second timeout. This way, your processes will crash
> rather than deadlock, which at least gives you a clear indicator that
> something went wrong, and sometimes even lets the program continue.

I think timeouts are almost always the wrong thing and tend to be overused,
but that's a different discussion.

-- 
David Hopwood <david.nospam.hopwood at blueyonder.co.uk>




More information about the e-lang mailing list