Semantics of READ and TCP Streams: Past And Future

In one of those bittersweet feelings of catharsis... all the AWAKE handlers are now gone.

That means no more wrote and no more on-wake-up or sync-op...or the other nightmares that were easy to get out of whack.

Because the code has historically been so brittle I was worried that I'd end up spending weeks dealing with the fallout of the change. But it took just about a day...and every change I've made has just made the HTTP and TLS code more accessible. Which was a relief.

What Were AWAKE Handlers?

AWAKE handlers were a bizarre twist on a fairly mundane idea of callbacks. Callbacks are what you get stuck with in single-threaded codebases when you want to do something asynchronous, as in a very basic JavaScript timer:

setTimeout(function () {
    console.log("10 seconds later...");
}, 10000);

Due to being single-threaded, you won't get that message unless you yield to the event loop...by returning out of all the stacks that are executing. Rebol didn't have the option of returning out of all the stacks (because that would just end the program). So instead the event pump would run when you called an explicit WAIT function.

But rather than passing in arbitrary free functions like JavaScript with each request, every asynchronous request had to be connected to a PORT!. There was one callback function that served all requests of all types on that port. It was a member of that port called AWAKE.

In explaining the EVENT! datatype, I pointed out the only information you get in the callback is implicitly the port, and a word (unless it's a GUI event, in which case you get some coordinates and which GOB! was clicked).

So this pretty much means you have to assume that whatever notification you're getting is for the last request of that type that was made on that port. Practically this means you can only have one request in flight at a time. And the event notification itself doesn't carry any data, the only place you can look for data is in the port itself.

It Was A Dead End to Asynchronous Programming

Whether you could read the R3-Alpha code well enough to critique it or not, you can certainly judge by the results:

Little was achieved in Rebol codebases on top of these foundations that wasn't essentially synchronous. It just got harder and more confusing to do synchronous things.

I can't speak too much to how it interacted with the GUI, other than to know by hearsay that it was painful.

In the core build, the only thing that wound up being asynchronous was getting timeouts. And generally the only reaction to timeouts was to error. You can work that into synchronous methodology pretty well.

Putting the Design In Context

If you're convinced that each recursion of a Rebol stack needs to line up with a recursion of the C stack, then your options for how to do asynchronous programming are limited.

They are especially limited if you want to be able to run asynchronously on systems without linking to a threading library. Threads let you do other stuff when a blocking READ operation happens. Without them, you're going to need callbacks and a message pump...it's very Windows 3.1

People coming from an embedded background can be resistant to threads, because they are thinking of what it takes to run on bare metal... no operating system, so no threads. And on operating systems with threads they have a pretty high cost; threads need their own stacks. So you might be averse to them even then.

When you look at what happened with JavaScript moving from callbacks to ASYNC/AWAIT, it's clear that the direction people are moving into is to express themselves synchronously and make the ability to interrupt the code come from the language substrate itself. For those who stress over the cost of OS threading, doing "green threads" (as in Go) offer an answer...and I've made arguments for why the "stacklessness" it would take to implement that is critical for other reasons.

3 Likes