[e-lang] E-like asynch I/O

Dan Bornstein e-lang@mail.eros-os.org
Wed, 8 May 2002 17:17:55 -0700


[Please forgive my probably-bad syntax and naming choices; I can't keep
track of the latest conventions.]

One of the things I've been particularly waiting for in E is for it to have
a non-Java-centric and more particularly E-like interface to the underlying
OS's I/O facilities. Since it hasn't seemed to happen spontaneously, I
thought I might try a little explicit nudging. I'm not sure I'll have time
to contribute code to the effort, but if I do, I'd like it to be something
that people more-or-less agree is more-or-less the right direction to go
in.

What I hope to see is something along these lines: In the standard E
environment, if it is configured to allow I/O at all, it will have bindings
for two pairs of objects, namely near and far implementations of file and
network I/O. The objects would respond mostly to maker-type messages, such
as "makeFile" or "makeTcpServerSocket." From both, one could derive input
and output streams (through a layer or so of indirection), with standard
interfaces across all of them. (That is, an input stream gotten from a near
file would respond to the same messages as an input stream gotten from a
far TCP socket, etc.) Conversion between bytes and characters would happen,
as in Java, with reader/writer wrappers.

The near objects would behave as you'd expect: you can make immediate
calls on them, and that might cause the Vat to block. You can also make
eventual calls on them, but, while that won't make the Vat block immediately,
it could make it block when those calls' turns came up.

The far objects would effectively behave as if they are in a different Vat,
though the actual implementation can probably keep the impedence layer
pretty minimal, especially if the majority of objects passed back and forth
are selfless (pass-by-copy? deep-frozen? you know what I mean). (I suspect
it will become prudent to have an immutable byte list object.) That is,
near calls will fail, and eventual sends will immediately succeed by
returning a promise, and all the usual promise foo applies.

With the assumption that the far version of the network maker is called
"asyncNet" and no further ado, here's a stupid but illustrative example
(attached below). Please rip it to shreds.

Thanks.

-dan

##########

/** 
 * Get the full response, as a string, from making an HTTP GET request on the
 * given host/port with the given path.
 */
def webGet (hostname :string, port :1..65535, path :string) :string
{
    // resolve the host name to a usable address
    def addr := asyncNet <- resolveAddress (hostname)

    // make an unconnected tcp socket
    def sock := asyncNet <- makeTcpSocket ()

    // connect it to the desired host / port
    sock <- connect (addr, port)

    // write out the request, wrapping the stream in a writer more for
    // convenience (the ability to use strings) than anything else
    def outStream := sock <- getOutputStream ()
    def writer := makeWriter (outStream, "US-ASCII")
    writer <- write ("GET ")
    writer <- write (path)
    writer <- write (" HTTP/1.0\n\n")
    writer <- close ()

    // get the input stream, and wrap it in a reader, in order to get
    // characters instead of bytes
    def inStream := sock <- getInputStream ()
    def reader := makeReader (inStream, "US-ASCII")

    // the result, and its value so far
    def [resultPromise, resultResolver] := makePromise ()
    def sofar := ""

    def readSomeStuff :void ()
    {
        // (eventually) returns a string of up to N chars or null if at eof
        stuff := reader <- read (1000) 

        when (stuff) -> done (realStuff)
        {
            if (realStuff != null)
            {
                // stuff got read; append it and iterate
                sofar += realStuff
                readSomeStuff <- run ()
            }
            else
            {
                // got the eof indicator; resolve the promise
                sock <- close ()
                resultResolver resolveTo (sofar)
            }
        }
    }

    // kick off the reading
    readSomeStuff ()

    // hand back the promise for the result
    resultPromise
}

milkHome := webGet ("www.milk.com", 80, "/")

when (milkHome) -> done (s)
{
    print (s)
}