More architectural issues... Now that I have the basic message passing and blocking policy working, I'm investigating how to integrate my little lib with asynchronous events in a (relatively) portable manner.
One of the most interesting and portable solutions seems to be 'libev'. It's a high-performance c-library that allows you to integrate asynchronous timer, device and other events into your application. It integrates with the operating system's select/poll subsystem and allows a lot of customization including the integration of co-routine subsystems (very similar to axon). Part of the trick is figuring out how to intersperse co-routine yields with async polls so that the whole system only blocks when all of the co-routines are WAITING, but then wakes up the co-routine scheduler when an async event puts any co-routine back into run mode. That has to be balanced with periodic checks with the async status so that components that require async feeds don't starve. As soon as I decode the developer information on the co-routine integration I'll post back here with what I find. JB On Sep 29, 11:48 am, erisian <[email protected]> wrote: > Okay... > > I love replying to my own posts :-) > > I've modified my axon port so that it schedules according to a rather > well-known message passing kernel policy: > > 1. The sender transmits to the receiver via a FIFO buffer. > 2. The sender only blocks if it tries to send on a full FIFO > 3. The receiver blocks if it reads from an empty FIFO > 4. Transmitter and receiver can both check the state of the FIFO > before attempting a send/receive. > 5. The receiver can override the full buffer behavior by: > a) asserting the "ready" boolean property before yielding, which > will allow the sender to continue sending even on full FIFO > b) by overriding the "ready" boolean with a "ready" boolean > function which can take mitigating action to recover from the full > state. Some examples might be dynamically increasing it's maximum > FIFO size or correct from a data overrun condition by clearing out the > FIFO and resetting the pipeline state etc > c) by overriding the "ready" with a function and setting the size > to 0 or 1 so as to simulate an actual callback or interrupt function. > Though I haven't tried this yet, it was interesting that it just seems > to naturally fall out of the code structure. (One of the reasons I > ported axon was to avoid having to deal with nasty callback > structures.) > > I might be over-architecting in feature 5, but it looked like a > natural interrupt-like capability for out-of-band processing. > > I'm writing the unit tests right now and might do one refactoring to > clean up single responsibility and encapsulation issues. > > As soon as I get something cleaned up a bit with unit tests I'll post. > > JimB > > On Sep 17, 4:57 pm, Jim Burnes <[email protected]> wrote: > > > Michael, > > > Thanks for the Kudos and I'm glad to hear someone is still out there. > > There are a few architectural issues I'd like to clarify. > > > As I was going through the development process a couple of ideas occurred to > > me. > > > 1. Should I just use a string inbox and dynamically convert the messages to > > their final values inside the components, or go for the gold and use D > > templating to allow you to create mailboxes that are type specific? > > > I decided to allow you to use D templates to create typed mailboxes. My > > thinking was to allow the compiler to make sure inboxes and outboxes matched > > types. In order to make it possible for users to dynamically match up an > > inbox with compatible outboxes, I register the outbox with a generic message > > bus and alllow introspection to take care of suggesting possible matches > > for dynamic programming. (I can use Tango lisp-like filtering mechanisms to > > find conforming mailboxes). > > > One of my concerns was the extra complexity required in a generic postman > > process to dispatch a type-specific send. I actually tried that and while > > it worked, it was really awkward because I had to first link the > > type-specific mailbox into the generic postman and then get the generic > > postman to dispatch the type-specific send in the outbox. > > > Well that was all resolved when I read a little further in your tutorial. I > > realized that by optimizing the postman out of the process and doing direct > > deliveries when outbox.send is called the whole problem evaporated. (The > > only downside of that is that message sends are not synchronous. An outbox > > can send 500 messages to a single inbox before a yield. In that sense it's > > an asynchronous bus, which brings with it certain issues, but avoids a lot > > of others.) > > > The advantages: Much higher performance becase I don't have to > > serialize/deserialize the messages when I send them. I can create a fractal > > image compression scheme and stream through an Outbox!(FractalDiff) or > > something. Pretty fast as long as the whole yield mechanism is relatively > > small compared to the size of the object being sent or computation being > > performed. > > > The downside: The rest of the system has to be tweaked slightly to keep > > track of Variant types. For example, if I have a Component that has one > > inbox of type SIGNAL, one inbox of type float, one outbox of type int and > > one outbox of type string I can't just create a linkedlist of those. > > > You can see that in the component definition. My current solution is to > > create a generic interface called "IntrFace" that implements a minimal > > inbox, outbox behavior and have all of the mailboxes implement that > > interface. Then I can create a LinkedList!(IntrFace) and everyone is > > happy. That probably seems obvious to you, but when you've been doing > > Python for a long time you make assumptions about the capabilities of the > > language itself. Type safe systems can be sticklers. :-) It's a little bit > > of pain now, for a (hopefully) big payoff later. > > > 2. Another issue that I'm still undecided on is what to do when: > > + An outbox wants to send to an inbox, but the inbox is full. (It's a > > linked list so it's only "sort of full") > > + You perform a read on an empty inbox (without checking for available > > content) > > > a) In the first case, I provide a "ready" boolean on the inbox. It answers > > the question "you're full, do you still want me to send?". The default > > value is "false", so the sender just ignores the inbox/receiver. It can be > > changed on-the-fly by the component when it thinks it might be busy or when > > its doing something intense. (rate adaption etc) The nice part is that the > > "ready" boolean can be overridden with a boolean function so that the > > receiver gets one last chance to adapt to the situation (extend its inbox > > size, change its highwater mark, flush the inbox and consider this an > > overrun condition etc). If it's able to adapt to extend the inbox it > > returns true. > > > b) When reading on an emtpy inbox, I'd like to have inbox.read() check its > > own FIFO length. If it's zero, then do a Fiber.yield() until something > > becomes available. The problem is that you can't just do a Fiber.yield(), > > you need to set some flag on the component to say its I/O waiting so it > > won't be rescheduled. Then of course you have to say what its waiting on so > > it won't be dispatched for the wrong inbox and have to sleep again. Levels > > of complexity etc. In the end I'd just like to remove the mandatory > > requirement that a component must check its inbox size before reading. It's > > a lot of redundant noise if it's not too difficult to program around. Of > > course the inbox still has the ability to check its own size whenever it > > wants to. If it works, inbox.read() behaves a lot like a blocking system > > call. That simplicity demands complexity in the scheduler. > > > (sorry if this is running long, but these are the architectural issues that > > run through my mind. believe it or not, Axon/D or Dendrite or whatever is > > just a subsystem for another simulation I'm building). > > > 3. Another "feature" I considered and then decided not to implement at the > > mailbox layer was delivery to multiple inboxes from the same outbox. At > > first it looked like a great idea. I would get publish/subscribe for free. > > Unfortunately it complicates the delivery mechanism as welll as constraining > > the "publisher" to stick around until all of the content has been consumed. > > Eventually I decided that a publisher is a special kind of component that > > subscribers register themselves with. The producer component only has to > > produce content, send it to the publisher and could simply quit executing. > > The publisher transmits its inbox to all listening subscribers. That way I > > decouple the producer from the consumer. This model also allows a special > > Pool component that does round-robin delivery to listeners. Such a > > component enables pure tuple space work pools allowing worker components to > > pick up some workunit, process it and come back for more. Combine that with > > network and multicore components and distributed parallel processing becomes > > possible. > > > 4. I've also considered pass through delivery, but haven't had a use case > > for it yet (though I won't be suprised if it presents itself.) > > > I want to clean up the code I've got and then I'll shoot it to you. > > > Jim Burnes > > Ft Collins, CO > > (erisian) > > > On Thu, Sep 17, 2009 at 3:30 PM, Michael Sparks <[email protected]> wrote: > > > Hi Erisian, > > > > Let me just say that I think this is awesome, and I'm very glad to hear > > > that > > > Kamaelia's been helpful to you here. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "kamaelia" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/kamaelia?hl=en -~----------~----~----~----~------~----~------~--~---
