Thursday, March 9, 2006

Reading List

Over about the last year, I've read:

  1. Collapse: How Societies Choose To Fail Or Succeed. Jared Diamond.

  2. Twisty Little Passages: An Approach to Interactive Fiction. Nick Montfort.

  3. The Legacy Of Heorot. Larry Niven, Jerry Pournelle, Steven Barnes.

  4. Choke. Chuck Palahniuk.

  5. The Extravagant Universe. Robert P. Kirshner.

  6. Knife Of Dreams. Robert Jordan.

  7. Dread Empire's Fall: Conventions of War. Walter Jon Williams.

  8. Cosmonaut Keep. Ken Macleod.

  9. The Island of the Day Before. Umberto Eco.

  10. Paths To Otherwhere. James P. Hogan.

  11. Moonseed. Stephen Baxter.

  12. The Zap Gun. Phillip K. Dick.

  13. Germinal. Émile Zola.

These are pretty much all worth a read, although if you read just 12 of them, I'd give The Island of the Day Before a miss. Germinal is probably the best of the bunch, but Collapse is the most educational, Knife of Dreams the most indulgent, and Conventions of War the most fun (but it's the last in a trilogy).

By way of meaningless comparison, about 46KLOC that I've checked into source control over the same period of time has survived to this evening.

Wednesday, March 8, 2006

Axiom Concurrency

Over the last several days, Axiom's concurrency story has come together quite a bit. This is something Divmod has put off, fairly confident that it would be easy once the time came. And hey, we were actually right (that's always fun). Quotient's fulltext indexer is now implemented as a second process which is automatically brought up and down with the main server and which is handed work to do through the Axiom database. This is done in a generic fashion with Axiom framework code doing most of the heavy lifting (like repeatedly adding one to an integer and comparing two numbers to see which is greater), so the actual application-level code in Quotient ends up doing something like this (actually, exactly this) in order to take advantage of the multi-process aspect of the system:

def installOn(self, other):
super(SyncIndexer, self).installOn(other), iaxiom.REMOTE)

def indexMessage(self, message):
# Do the actual full-text indexing of `message'
processItem = indexMessage

The code barely even has to be aware of the fact that anything is going on in a separate process: it simply declares its interest in MessageSource and the fact that it has no objection to a potentially esoteric execution context. Any application can take advantage of this feature just as easily to split computationally expensive or high-volume disk-bound tasks into a separate operating system process, where they will not interefere with code that is latency-sensitive (like a network server) and where they can take advantage of additional CPU or disks, providing for greater parallelization of the task.

Of course, like any user of the reliable listener system, be they local or remote, the indexer:

  • will be run as quickly as the system can manage, taking into account the interactive requirements being placed on the server as well as the other reliable listeners which have been created and are also vying for runtime;

  • can be throttled back if another task takes precedence;

  • can be monitored and reported on for user feedback or administrator introspection;

  • can be prioritized relative to other kinds of listeners;

  • can be reset entirely and allowed to re-process all old messages (useful when, for example, the fulltext index becomes corrupt or is replaced by a superior system);

  • or just messages which caused an error and due to code changes may now be processable without one.

All this is due to the fact that the fulltext indexer is not directly responsible for finding messages to index, but has merely subscribed to a source of messages, and it is up to that subscription to decide what and when messages are given to it to be indexed.

Monday, March 6, 2006

JavaScript source lines in Python tracebacks

Added support to Athena today for generating tracebacks like this one:

2006/03/06 19:44 EST [HTTPChannel,250,] Traceback (most recent call last):
File "", line 0, in

--- ---
File "", line 0, in

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/runtime.js", line 39, in ()
File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/divmod.js", line 136, in ([object Object])
return methodFunction.apply(this, args);
File "", line 0, in apply([object Object],[object Array])

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/defer.js", line 137, in callback([object Object],[object Object])
File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/divmod.js", line 136, in ([object Object])
return methodFunction.apply(this, args);
File "", line 0, in apply([object Object],[object Array])

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/defer.js", line 134, in _startRunCallbacks([object Object],[object Object])
File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/divmod.js", line 136, in ()
return methodFunction.apply(this, args);
File "", line 0, in apply([object Object],[object Array])

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/defer.js", line 108, in _runCallbacks([object Object])
self._result = callback.apply(null, args);
File "", line 0, in apply(null,[object Array])

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/athena.js", line 259, in ([object Object])
action.apply(null, actionArgs);
File "", line 0, in apply(null,[object Array])

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/athena.js", line 191, in ("Nevow.Athena.callByAthenaID","s2c241",[object Array])
result = funcObj.apply(null, funcArgs);
File "", line 0, in apply(null,[object Array])

File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/athena.js", line 609, in (14,"setStatus",[object Array])
var widget = Nevow.Athena.Widget.fromAthenaID(athenaID);
File "/home/exarkun/Projects/Divmod/trunk/Nevow/nevow/athena.js", line 601, in (14)
throw new Error(nodes.length + " nodes with athena id " + widgetId);
File "", line 0, in Error("0 nodes with athena id 14")

nevow.athena.JSException: Error: 0 nodes with athena id 14

Still a bit unsightly: function arguments probably shouldn't show up, anonymous functions should be reported as "" instead of "", every other frame should be omitted (or Athena shouldn't insert those in the first place, ah but it really needs to). It'd also be nice to have the first stanza filled out (the bit before --- ---), as it is with real Python exceptions.

Anyway, the coolest part is how emacs can parse these and automatically open the appropriate JavaScript file for me when an unhandled error occurs in the browser during debugging.

Sunday, March 5, 2006

Saturday, March 4, 2006

cProfile and kcachegrind

Armin Rigo's cProfile is pretty cool, and will be in Python 2.5. KCacheGrind is basically the most awesome profile analysis tool I have ever seen. But how can you use cProfile with KCacheGrind? Itamar and I took a patch against lsprof (cProfile's immediate ancestor) from David Allouche's blog, changed it to be a stand-alone module and updated it to work with cProfile. It's pretty awesome. If you are profiling Python you almost certainly cannot live without it.