[Date Prev][Date Next][Thread Prev][Thread Next]
- Subject: Re: Idea for an event loop library
- From: Jay Carlson <nop@...>
- Date: Fri, 11 May 2012 09:10:30 -0400
On May 11, 2012, at 5:10 AM, steve donovan wrote:
> On Fri, May 11, 2012 at 7:20 AM, Jay Carlson <firstname.lastname@example.org> wrote:
>> Let me underline that. Unless you know otherwise, any function invocation can pause; if you have made any assumptions about the state of the world, you need to re-establish those invariants after each call because it may have been a long time since the start of the call.
> And this may be a harmless-looking sleep() call ...
MOO's task system allocates a fixed number of ticks to a task (ticks are analogous to bytecode instructions). Running out kills the task by raising an uncatchable error. This prevents runaway code from locking up the whole hundreds-of-people-connected server. A task calling sleep (including for 0 seconds which is just a yield) gets a full supply of ticks back on wakeup.
A very commonly called utility function is :suspend_if_needed, which looks at ticks_left and sleeps if running close to the bottom of the reservoir of ticks. The formative environment was a SPARCstation 1, and the number of ticks available there had to be pretty low to preserve interactive response. If you had a function iterating over some large number of objects, you'd pretty much have to sprinkle :suspend_if_needed calls through it.
I think I was the person to describe the overall task scheduler as using a vindictive algorithm: if you had used a second of process time, none of your tasks were candidates to run until everybody else's tasks had used up a second of process time. So your sleep(0) call may very well return much later than 0 seconds, and code after :suspend_if_needed to be aware of that. There were a lot of bugs, some security-sensitive, as a result. It didn't really burn people that often, but when it does, it was memorable.
The real fun was calling utility functions. People might not mention in their documentation that they took the liberty of calling :suspend_if_needed for you; the uncatchable out-of-ticks exception was not popular. You might want separate :sort and :sort_with_suspend functions. And this "might yield" property of functions propagates up through the call chain until inevitably somebody forgets to mention it. Add to this that you were programming in a live, changing system and that bogus programmers could change their objects to yield (or sleep forever) in the analog of object:tostring() and it seems pretty frightening.
Looking at it today from the perspective of a language designer concerned with correctness, I am horrified at all the things which could go wrong. But it pretty much works. I'd say the stakes were low, but it was used in some mission-critical collaboration tools, and the commercial replacement systems, written in Java, were not popular with users, or with sysadmins who had become accustomed to years-long uptime rather than daily reboots. But motivation for Java was it was an enterprise-quality environment. Go figure.
> Go has an interesting hybrid;
> memory is shared between goroutines, but people are encouraged to use
> channels to send data. (S also uch green threads use OS threads when
> anything blocking happens)
I may be misunderstanding them, but in this context goroutines look like the inverse of an "with_no_suspend [...] end" block: they license the target to block; if they do not block explicitly or implicitly (being preempted) they can run inline.
: Atomicity of basic operations overrides running out of ticks or seconds. On one memorable occasion I made a change to an object prototype which a lot of objects inherited from; this caused a bunch of swap thrashing. It blocked all server execution for around 20 seconds. After, I got a couple of IMs; I typed a couple of responses, but there was no response to my commands. I decided to watch the world go by. 45 minutes later, the scheduler decided I had been punished enough, and let my commands through....