Summary
This code release introduces an action for raising events to personal event networks in parallel. The makes event network federation through subscription practical.
Last month, I wrote about federating personal event networks through event subscription and motivated the idea with an example called the On-Call TA scenario. The idea is that a personal event network for the class distributes meeting requests from students to each of the TAs for the class as shown in this diagram:
The TA's subscribe to events from the class' personal event network. The Dispatcher app in the class network sends events to each subscriber by looping over the subscriber list. The action, send_event was a user-defined action that used http:post() to do the work. Here's what the definition looked like:
send_event = defaction(subscriber, dom, type) { configure using attrs = {} mk_esl = function(token, d, t) { eid = math:random(9999999); a = [token, eid, d, t]; "http://cs.kobj.net/sky/event/" + a.join("/"); }; http:post(mk_esl(subscriber,dom,type)) with params = attrs };
This works fine, but results in a number of HTTP POSTs being made serially--one for each subscriber. This made running the rule quite slow. I determined that what we needed was a built-in send() action in the event library that made the HTTP POSTs more intelligently. If you read my post on asynchronous events in Perl from last week, that's what I was working on.
I'm happy to announce that the event library now supports the send() action. Using it in the code for the On-Call TA demonstration wouldn't change it very much. Here's what it looks like now:
rule dispatch { select when explicit schedule_inquiry foreach subscribers setting (subscriber) pre { resp_cookie = math:random(99); } if(onnow("On Call", subscriber{"calendar"})) then { event:send(subscriber,"schedule","inquiry") with attrs = {"from" : event:attr("From"), "message": event:attr("Body"), "cookie": resp_cookie }; } fired { set ent:return_receipt{"x"+resp_cookie} event:attr("From"); } else { ent:missed_call_count += 1 from 0; } }
Written like this, the HTTP POSTs will happen in parallel and the execution time of the rule is just a little more than the time it takes to make the longest HTTP POST. That's a big win and means that processing longer subscription lists is now realistic.
One of the problems with rules like the one shown above that loop is that the postlude is executed each time through the loop. For this rule that's not a problem because when the rule fires, the operation is idempotent and when it doesn't we actually want to increment the counter each time there's a miss. Often, however, this is a problem. Supposed, for example that we wanted to raise an explicit event indicating that the subscribers had been notified so we could clean up some persistent variables or something. Raising an explicit event is not idempotent.
Consequently, I also added a new guard condition for postlude statements called on final that is only true the last time that a rule executes. So we could write something like this:
rule dispatch { select when explicit schedule_inquiry foreach subscribers setting (subscriber) pre { resp_cookie = math:random(99); } event:send(subscriber,"schedule","inquiry") with attrs = {"from" : event:attr("From"), "message": event:attr("Body"), "cookie": resp_cookie }; } always { raise explicit event subscribers_notified on final } }
This code will send the event to all the subscribers and only raise the explicit:subscribers_notified event once when everything is done. The on final guard even works with nested foreach loops.
Even with this new action, the platform is not ready for subscription lists much longer than a dozen or so. I've been thinking of solutions for subscription lists in the thousands, but they're not ready yet...nor do we need them yet.