Archive for the ‘Programming’ Category

Programming Must Be An Open System

I decided I needed an hour break from Scala hacking, and I am about halfway finished with Sean Carroll's From Eternity to Here, which goes on at great length about entropy as it relates to time's arrow. So for the fun of it I whipped up a simulation of an Ehrenfest Urn using Processing.js.

Check it out here (requires a browser supporting canvas).

Now that I'm done, there are a couple of things I find amazing about this.

  • I felt like I needed a break from being paid to write Scala. As opposed to, like, Visual Basic, Java, C++, or something like that.
  • Within an hour I was able to download processing, learn the basics, and hack this animated demo together. And put it on a web page, viewable by pretty much anybody that I care to reach. Back in the early aughts I probably spent fifty or sixty hours just trying to figure out how to step debug javascript.

From eternity to here apparently involves a massive improvement in the state of the programming art. Everything is amazing and nobody's happy.

PGProxy: A Testing Proxy for Postgres

I've released a package that I've been using for well over a year now for the purpose of writing functional test suites against applications using Postgres. The executive summary is that PGProxy allows you to write tests that are transactionally isolated from one another, without doing anything special in your application code.

This project is aimed more at functional tests of a website (using something like Selenium) than it is for unit tests of a single class or module. In those cases, using mock objects or other strategies is more viable. But, you could certainly use PGProxy in those scenarios as well.

You can get it on github here: http://github.com/mcfunley/pgproxy.

PGProxy is written in Python using Twisted, and has its own extensive set of unit and functional tests.


Why You Would Want This

If you have a sufficiently large set of functional tests written by a sufficiently large team, eventually naively-written tests will begin to interfere with one another. A really simple example of two tests that would interfere with one another would be:

  • Test A: Tests a workflow for user "steve"
  • Test B: Tests deleting the account for user "steve"

Clearly, if we don't take any special precautions in our test suite, Test A will never succeed if it is run after Test B. And if we require that Test A always runs before Test B, then we're forced to recreate our fixture database in between test runs. And this is just two tests–when you're talking about thousands, the potential interactions can be huge. Not to mention that depending on big your fixture databases are, creation at the start of every run can be a pain.

So eventually you will probably decide it would be better to restore the fixture data to a known state in between test cases. There are a few different ways to try to accomplish this:

  1. Restore the fixture database between test cases using something like CREATE DATABASE .. WITH TEMPLATE.
  2. Have each test be responsible for undoing whatever it does.
  3. Have each test be responsible for creating any data that it's going to use.
  4. Don't write tests for user "steve," but rather locate a user in the fixture data that meets the criteria that you need.
  5. Make each test work inside of a transaction, and roll the transaction back when the test is completed.

There are problems with all of these approaches. 1) is very slow. 2) is very tedious for developers and error prone. 3) is similarly tedious and can be slow, depending on how much logic creating your entities entails. 4) is really just moving the goalposts, because tests are still going to interfere with each other. 5) works to the extent that you can have all of your database access code share a connection and to the extent that your code does not try to use its own transactions.

But! I am writing this to tell you about an exciting new option, namely, "do something crazy." PGProxy is that crazy thing, and it works pretty well.


How it Works

As mentioned above, your test case can only work inside of a transaction if you are able to use a single connection per database per test case. If your test case is making calls to multiple processes that all want to use your fixture database, that is a pretty difficult thing to do.

PGProxy solves the problem by, you guessed, it, proxying all of your database connections. So if you have a PHP site running in Apache and a scala service running in Jetty, they can now share database connections in your tests. And consequently, they can share a transaction.

The other issue that comes up in using transactions to make test cases is that it's pretty common for the code that you're testing to want to use its own transactions. PGProxy solves this by rewriting transaction usage within the test case into SAVEPOINT usage. In other words, if you have a test that runs this SQL:

BEGIN;
update users set username='chuck' where username='steve';
COMMIT;
BEGIN;
update users set password='foo' where username='chuck';
ROLLBACK;

PGProxy will rewrite that to this:

BEGIN; -- my test case
SAVEPOINT x;
update users set username='chuck' where username='steve';
RELEASE SAVEPOINT x;
SAVEPOINT y;
update users set password='foo' where username='chuck';
ROLLBACK TO SAVEPOINT y;
ROLLBACK; -- my test case


Running the Proxy

There are a few ways to run the proxy. If you are writing your test suite using python (ie, using unittest), you can set up your test runner like this:

from __future__ import with_statement
import pgproxy

this_dir = os.path.realpath(os.path.dirname(__file__))
pidfile = os.path.join(this_dir, 'pgproxy.pid')
logfile = os.path.join(this_dir, 'pgproxy.log')

def run():
    # this will shut down the proxy when the tests complete.
    with pgproxy.run(pidfile=pidfile, logfile=logfile):
        run_test_suite()

def run_test_suite():
    # this should actually run your tests
    pass

if __name__ == '__main__':
    run()

Or you can use something like this script to start a standalone pgproxy process:

#! /usr/bin/env python
import pgproxy
import os

this_dir = os.path.realpath(os.path.dirname(__file__))
pidfile = os.path.join(this_dir, 'pgproxy.pid')
logfile = os.path.join(this_dir, 'pgproxy.log')

pgproxy.run(listenPort=5433, serverAddr=('localhost', 5432),
            pidfile=pidfile, logfile=logfile)

In both of these cases, PGProxy is configured to accept connections on port 5433, and to connect to the Postgres server running on port 5432. In these examples you would tell your application to connect to port 5433.

In order to run PGProxy, you need Twisted version 8.1.0 or later.


Test Harness Integration

PGProxy accepts two special queries that signal the start and the end of tests. Your test suite will need to invoke these in setUp and tearDown (or whatever the equivalents are in the language / framework that you're using). Here's a unittest example:

class TestCase(unittest.TestCase):
    def setUp(self):
        self.query("BEGIN TEST '%s'" % self._testMethodName)

    def tearDown(self):
        self.query("ROLLBACK TEST '%s'" % self._testMethodName)

    def test_something(self):
        # now this has a transaction and can't do any serious damage
        # to the fixture data.
        pass

As you may have noticed above, the BEGIN/ROLLBACKS are sent to postgres with comments stating which test is running, which can be pretty handy if you find yourself needing to look at the postgres logs to debug something. Here you would see:

BEGIN; -- test_something
ROLLBACK; -- test_something

Since setUp and tearDown are frequently overridden by developers for other purposes, I generally like to use a metaclass to wrap test cases in transactions instead. This way if a developer forgets to call the base test case's setUp method, it's no big deal for the rest of the suite. I'll leave that as an exercise. You get the idea.


A Word About Twisted

Lord knows I have a complicated opinion of all things Twisted, and maybe someday I will write something about that. And by "someday" I mean I am almost certainly never going to, because I have enough trouble staying out of nerd fights on the internet.

But I have to say that for this project, with the precise set of requirements that it had, and taking as a given my pre-existing wealth of experience with Twisted, things worked out great. This was a from-scratch rewrite of my first version, which was written using asyncore. The asyncore version was riddled with obscure race conditions, and it turned out to be much easier to just rewrite the damned thing using an event-driven framework than it ever was to debug the original. There was not a better choice of Python framework for this project, though I did toy with the idea of using scala. Anyway, I hope this praise for Twisted doesn't come across as excessively faint.


The End

Enjoy! Don't hesitate to drop me a line if you find this useful or have bug reports.


Functional vs. Imperative Red-Black Tree Insertion

MarkCC posted an insert algorithm for red-black trees in Haskell yesterday. Now I don't want to come across as anything resembling an expert here but I felt honor-bound to raise the point that there is a far simpler algorithm described in Chris Okasaki's 1999 paper Red-Black Trees in a Functional Setting.

It goes basically like this:

data Color = Red | Black deriving (Show, Eq)

data RedBlackTree a =
    Empty | Node Color (RedBlackTree a) a (RedBlackTree a)
    deriving (Show, Eq)

insert :: Ord a => a -> RedBlackTree a -> RedBlackTree a
insert elem t = makeBlack (insert' elem t)
	where makeBlack (Node _ a y b) = Node Black a y b

insert' elem Empty = Node Red Empty elem Empty
insert' elem s@(Node color a y b)
    | elem < y = balance color (insert' elem a) y b
    | elem > y = balance color a y (insert' elem b)
    | otherwise = s

balance Black (Node Red (Node Red a x b) y c) z d =
    Node Red (Node Black a x b) y (Node Black c z d)
balance Black (Node Red a x (Node Red b y c)) z d =
    Node Red (Node Black a x b) y (Node Black c z d)
balance Black a x (Node Red (Node Red b y c) z d) =
    Node Red (Node Black a x b) y (Node Black c z d)
balance Black a x (Node Red b y (Node Red c z d)) =
    Node Red (Node Black a x b) y (Node Black c z d)
balance color a x b = Node color a x b

I will not try to replicate the contents of the paper here, it's very short so I encourage you to go read it. The gist of it is as follows.

The reason the Haskell algorithm is simpler is because in functional languages, destructive updates to the tree are impossible. This renders the (relatively complex) optimizations that are commonplace in imperative implementations pointless.

The functional implementation has other advantages, namely: beauty, simplicity, persistence, and all of the other things we choose when we give up pointer manipulation.

Memetastic

At the behest of Ira Pfeifer here is every programming language I have ever learned. I have grouped these into families since I have met people that have touted the nine variants of COBOL that they know as separate languages and I have considered all of these people to be deserving of horrible, horrible deaths. The grouping makes chronological order impossible.

  1. BASICs: TRS-80 variety, GWBASIC, QBASIC, VB6, VB.NET.
  2. C
  3. Assemblies: x86. And okay, MIPS in school. I have actually written MSIL by hand for production use, although I am not sure if that really belongs here.
  4. C++, to the degree that anyone other than Stroustrup, Stepanov, and Sutter actually knows this byzantine mess.
  5. Pascal, but I quickly reconsidered.
  6. Shell scripts: cmd.exe batch, Bourne, Monad
  7. Java
  8. ML's: SML, OCaml, F#
  9. PHP
  10. Coldfusion. Feel free not to count this. I just completely forgot that this ever existed and felt like listing it.
  11. Turing complete SQL's: T-SQL, plpgsql
  12. C#
  13. Lisps: Common, Scheme, and Emacs
  14. Python
  15. Haskell
  16. Javascript
  17. Erlang
  18. Ruby
  19. Horrible ideas that some enterprisey person should be in prison for: NAnt, Ant.
  20. Things that Wikipedia lists as languages but that might be going too far with this: YACC, XSLT, Matlab.

It's hard to pick favorites between Haskell and the Lisp variants. I can get a lot of work done in Lisps but appreciate Haskell for its brainsmashing purity. If I had to point out an obvious deficiency it'd be that I have yet to play with Factor.

elisp-ext.el

Well, if you use emacs long enough, eventually you realize that you have tens of thousands of lines of elisp lying around. I'm making a modest effort to dig myself out of this mess and what useful tidbits there are I will share with the world. Here is a very basic module that helps you get yourself in a predicament similar to my own.

;;;
;; elisp-ext.el
;;
;; Utilities for writing and debugging Emacs lisp. The
;; interactive functions are:
;;
;;   elisp:goto-definition - Jumps to the source for a
;;                           defun, defvar, etc.
;;
;;   elisp:edebug-at-point - Enable edebug for the symbol
;;                           nearest the cursor.
;;
;; When this is loaded, the sequence C-c C-g is bound to
;; elisp:goto-definition and C-c C-d is bound to
;; elisp:edebug-at-point in ielm and elisp-mode.
;;
;; by Dan McKinley, 2008
;; http://mcfunley.com
;;
;; This program is free software: you can redistribute it
;; and/or modify it under the terms of the GNU General Public
;; License as published by the Free Software Foundation, either
;; version 3 of the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;
(eval-when-compile (require 'cl))

(defun* elisp:read-symbol (default &optional
                (prompt "Symbol")
                (test 'intern-soft))
  (let (val)
    (setq val (completing-read
           (if default (format "%s (default %s): " prompt default)
             (concat prompt ": "))
           obarray test t nil nil
           (and default (symbol-name default))))
    (if (equal val "")
        sym
      (intern val))))

(defun elisp:goto-definition (sym)
  "Extends find-func.el to find any symbol (defaulting
to the one at point)."
  (interactive (list (elisp:read-symbol (symbol-at-point))))
  (cond ((fboundp sym) (find-function sym))
    ((facep sym) (find-face-definition sym))
    (t (find-variable sym))))

(defun elisp:edebug-function (fun)
  "Enables edebug on a function given its symbol.
This necessarily leaves the file containing `fun' open, since
edebug will not open files on its own."
  (when (subrp fun)
    (error "Can't edebug a C function, sorry."))
  (save-excursion
    (let ((library (symbol-file fun nil)))
      (destructuring-bind (buf . pos)
      (find-function-search-for-symbol fun nil library)
    (set-buffer buf)
    (goto-char pos)
    (forward-sexp)
    (edebug-eval-top-level-form))))
  fun)

(defun elisp:edebug-at-point (fun)
  "Enables edebug on a function, defaulting to the
`function-called-at-point'."
  (interactive
   (list
    (elisp:read-symbol
     (function-called-at-point) "Debug function" 'fboundp)))
  (message "Enabled debugging on %s." (elisp:edebug-function fun)))

(defun elisp:local-keys ()
  (local-set-key "\C-c\C-g" 'elisp:goto-definition)
  (local-set-key "\C-c\C-d" 'elisp:edebug-at-point))
(add-hook 'emacs-lisp-mode-hook 'elisp:local-keys)
(add-hook 'ielm-mode-hook 'elisp:local-keys)

(provide 'elisp-ext)

Rule of Thumb

So here's my method for determining whether or not a particular platform is worthwhile.

I ask myself, "how would David Letterman summarize this?" I know this doesn't seem very scientific, but it's a mask I have years of experience wearing. Trust me on this one.

When the curtain is raised to reveal a total square dancing around with a sign that says, "it's simply XML!" I ask myself, "is this anything?" Then I reflect for a second or two and decide, dismissively, that no. This is nothing.

Lakin Wecker has an alternative approach that involves thinking carefully and considering the lessons of history. I will re-evalutate my approach if our results ever differ significantly.

Native Posix Python Condition Implementation

So I wrote this replacement version of Condition using the native posix support. Event and Semaphore are both written in terms of Condition, so you can use this as a fast route to getting native versions of those synchronization primitives. (Note, though, that there is a native posix semaphore, so implementing it in terms of a condition variable is not really necessary.)

I have trained myself to ask, "why hasn't anybody done this before?" when writing this sort of thing. And as always there's a really good reason: for a percentage of applications that is probably very close to one hundred percent, the difference in performance between this and the pure python version (which is implemented using polling) is not going to amount to a hill of beans. That was indeed the case for the application where I was trying to put this to use, and I reverted to the much simpler python Condition.

But there was no way to know for sure that that was the case without trying this, so if you find yourself in a similar situation, here is the code.

#include "Python.h"
#include "pthread.h"
#include "structmember.h"

typedef struct {
  PyObject_HEAD
  int set;
  pthread_cond_t cond;
  pthread_mutex_t lock;
} ConditionObject;

static int cond_init( ConditionObject* self, PyObject* args,
              PyObject* kwargs );
static void cond_free( ConditionObject* self );
static PyObject* cond_acquire( ConditionObject* self );
static PyObject* cond_release( ConditionObject* self );
static PyObject* cond_wait( ConditionObject* self, PyObject* args );
static PyObject* cond_notify( ConditionObject* self );
static PyObject* cond_notifyAll( ConditionObject* self );

static PyMemberDef cond_members[] = {
  {NULL}
};

static PyMethodDef cond_methods[] = {
  { "acquire", (PyCFunction)cond_acquire, METH_NOARGS, "" },
  { "release", (PyCFunction)cond_release, METH_NOARGS, "" },
  { "wait", (PyCFunction)cond_wait, METH_VARARGS, "" },
  { "notify", (PyCFunction)cond_notify, METH_NOARGS, "" },
  { "notifyAll", (PyCFunction)cond_notifyAll, METH_NOARGS, "" },
  { NULL }
};

static PyTypeObject ConditionType  = {
  PyObject_HEAD_INIT(NULL)
  0,                         /*ob_size*/
  "_pthread_cond.Condition", /*tp_name*/
  sizeof(ConditionObject),   /*tp_basicsize*/
  0,                         /*tp_itemsize*/
  (destructor)cond_free,     /*tp_dealloc*/
  0,                         /*tp_print*/
  0,                         /*tp_getattr*/
  0,                         /*tp_setattr*/
  0,                         /*tp_compare*/
  0,                         /*tp_repr*/
  0,                         /*tp_as_number*/
  0,                         /*tp_as_sequence*/
  0,                         /*tp_as_mapping*/
  0,                         /*tp_hash */
  0,                         /*tp_call*/
  0,                         /*tp_str*/
  0,                         /*tp_getattro*/
  0,                         /*tp_setattro*/
  0,                         /*tp_as_buffer*/
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /*tp_flags*/
  "",                        /* tp_doc */
  0,                       /* tp_traverse */
  0,                       /* tp_clear */
  0,                       /* tp_richcompare */
  0,                       /* tp_weaklistoffset */
  0,                       /* tp_iter */
  0,                       /* tp_iternext */
  cond_methods,              /* tp_methods */
  cond_members,              /* tp_members */
  0,                         /* tp_getset */
  0,                         /* tp_base */
  0,                         /* tp_dict */
  0,                         /* tp_descr_get */
  0,                         /* tp_descr_set */
  0,                         /* tp_dictoffset */
  (initproc)cond_init,       /* tp_init */
  0,                         /* tp_alloc */
  0,                         /* tp_new */
};

static int cond_init( ConditionObject* self, PyObject* args,
              PyObject* kwargs ) {
  self->set = 0;
  int err = pthread_mutex_init( &self->lock, NULL );
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return -1;
  }
  err = pthread_cond_init( &self->cond, NULL );
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return -1;
  }
  return 0;
}

static void cond_free( ConditionObject* self ) {
  pthread_mutex_destroy( &self->lock );
  pthread_cond_destroy( &self->cond );
}

static PyObject* cond_acquire( ConditionObject* self ) {
  int err = 0;
  Py_BEGIN_ALLOW_THREADS;
  err = pthread_mutex_lock( &self->lock );
  Py_END_ALLOW_THREADS;
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return NULL;
  }
  Py_RETURN_NONE;
}

static PyObject* cond_release( ConditionObject* self ) {
  int err = pthread_mutex_unlock( &self->lock );
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return NULL;
  }
  Py_RETURN_NONE;
}

static PyObject* cond_wait( ConditionObject* self, PyObject* args ) {
  // for now timed waits are not supported (it's ignored)
  int err = 0;
  while( !self->set && err != EINVAL ) {
    Py_BEGIN_ALLOW_THREADS;
    err = pthread_cond_wait( &self->cond, &self->lock );
    Py_END_ALLOW_THREADS;
    if( PyErr_CheckSignals() ) {
      return NULL;
    }
  }
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return NULL;
  }
  Py_RETURN_NONE;
}

static PyObject* cond_notify( ConditionObject* self ) {
  self->set = 1;
  int err = 0;
  Py_BEGIN_ALLOW_THREADS;
  err = pthread_cond_signal( &self->cond );
  Py_END_ALLOW_THREADS;
  self->set = 0;
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return NULL;
  }
  Py_RETURN_NONE;
}

static PyObject* cond_notifyAll( ConditionObject* self ) {
  self->set = 1;
  int err = 0;
  Py_BEGIN_ALLOW_THREADS;
  err = pthread_cond_broadcast( &self->cond );
  Py_END_ALLOW_THREADS;
  self->set = 0;
  if( err != 0 ) {
    PyErr_SetFromErrno( PyExc_OSError );
    return NULL;
  }
  Py_RETURN_NONE;
}

static PyMethodDef module_methods[] = {
  {NULL}
};

PyMODINIT_FUNC
init_pthread_cond(void)
{
  ConditionType.tp_new = PyType_GenericNew;
  if( PyType_Ready( &ConditionType ) < 0 ) {
    return;
  }
  PyObject* mod = Py_InitModule( "_pthread_cond", module_methods );
  if( mod == NULL ) {
    return;
  }
  Py_INCREF( &ConditionType );
  PyModule_AddObject( mod, "Condition", (PyObject*)&ConditionType );
}

Haskell Mandelbrot Set

Some people sing carols every XXX-mas, I get bored and write the Mandelbrot Set program in whatever my favorite language happens to be that year. I thought the brevity of the output this year (Haskell) was kinda neat.

import Graphics.UI.GLUT
import Control.Monad
import Data.Int
import Data.Complex

iterations = 400

x // y = fromIntegral x / fromIntegral y

-- Divides [a] into [[a], [a], ...] with each sublist of length n,
-- except the last sublist which has length <= n.
chunkify n [] = []
chunkify n xs = let (xs', rest) = splitAt n xs
                in xs' : chunkify n rest

-- Converts a coordinate in screen space to a vertex.
pix2vert (Size w h) (x, y) = Vertex2 ((3 // w * fromIntegral x) - 2.0)
                             ((2 // h * fromIntegral y) - 1.0)

-- List of all of the vertices that represent screen pixels.
vertices :: IO [Vertex2 GLfloat]
vertices = get windowSize >>= \(Size w h) ->
           return $ [pix2vert (Size w h) (x, y) | x <- [0..w-1], y <- [0..h-1]]

-- Gets the color for a number of iterations.
color3 r g b = Color3 r g b
getcolor :: Int -> Color3 Float
getcolor iter | iter == iterations = color3 0 0 0
              | otherwise          = color3 (amt*0.5) amt (amt*0.5)
              where amt = iter // iterations

-- Returns the number of iterations <= the maximum iterations of the
-- Mandelbrot set at the given vertex.
mandel (Vertex2 r i) = length . takeWhile (\z -> magnitude z <= 2) .
                       take iterations $ iterate (\z -> z^2 + (r :+ i)) 0

-- plots one point.
drawVert v = do color . getcolor $ mandel v
                vertex v

-- draws all the vertices in slices (to update the display while drawing).
display' chunks = do mapM_ (\vs -> do renderPrimitive Points $ mapM_ drawVert vs
                                      flush) chunks
                     displayCallback $= display

-- draws the whole fractal
display = do clear [ ColorBuffer ]
             displayCallback $= (vertices >>= display' . chunkify 256)
             get currentWindow >>= postRedisplay

main = do
   getArgsAndInitialize
   initialDisplayMode $= [ SingleBuffered, RGBMode ]
   initialWindowSize $= Size 1200 1024
   initialWindowPosition $= Position 100 100
   createWindow "Mandelbrot"
   clearColor $= Color4 0 0 0 0
   matrixMode $= Projection
   loadIdentity
   ortho (-2) 1 (-1) 1 (-1) 1
   displayCallback $= display
   mainLoop

Screenshot:

Mandelbrot Set written in Haskell

Dan McKinley does not claim to be a Haskell expert and definitely doesn't claim to be an expert about Haskell graphics rendering.

How to Debug Python in GDB

Although I haven't found this to be necessary nearly as often as Windbg was for me on Windows, it's still somewhat handy to be able to look at a Python daemon or multithreaded program in GDB. I've had to set this up a few times now, and I've forgotten the steps each time, so here they are in one place.

Step 1 - Get a debug build of Python.

You need a debug version of the interpreter. This is what I do from source on *nix:

./configure --prefix=/usr/local/dbg
make OPT=-g
sudo make install

sudo ln -s /usr/local/bin/dbgpy /usr/local/dbg/bin/python

Step 2 - Fix the botched .gdbinit that comes with the Python source.

The Python source comes with a .gdbinit file (Misc/gdbinit), but it's broken in 2.5. Apply these changes, which will fix pystack to 1) work and 2) work on threads other than the main thread.

@@ -119,8 +122,8 @@

 # print the entire Python call stack
 define pystack
-    while $pc < Py_Main || $pc > Py_GetArgcArgv
-        if $pc > PyEval_EvalFrame && $pc < PyEval_EvalCodeEx
+    while ($pc < Py_Main || $pc > Py_GetArgcArgv) && ($pc < t_bootstrap || $pc > thread_PyThread_start_new_thread)
+        if $pc > PyEval_EvalFrameEx && $pc < PyEval_EvalCodeEx
         pyframe
         end
         up-silently 1
@@ -130,8 +133,8 @@

 # print the entire Python call stack - verbose mode
 define pystackv
-    while $pc < Py_Main || $pc > Py_GetArgcArgv
-        if $pc > PyEval_EvalFrame && $pc < PyEval_EvalCodeEx
+    while ($pc < Py_Main || $pc > Py_GetArgcArgv) && ($pc < t_bootstrap || $pc > thread_PyThread_start_new_thread)
+        if $pc > PyEval_EvalFrameEx && $pc < PyEval_EvalCodeEx
         pyframev
         end
         up-silently 1

Note that the locals still won't work. I'll post a diff that fixes those when I get around to doing that. In the meantime it's not too hard to poke through the python structures to get them.

On OS X you can just copy the result of this to ~/.gdbinit. Some linux distros might be cranky about where you put this and where you launch gdb.

Step 3 - Launch your program with the debug interpreter and attach.

From the command line you can attach with "gdb /usr/local/bin/dbgpy [pid]." I like gdba mode in emacs (see below - M-x gdba, then the same kind of command line).

gdba mode in Emacs

New MacBook Pro, Jealous?

I suppose I finally had enough of the carpal tunnel caused by typing '\' to delimit directories (yes, powershell helps a little, but not enough).

Or, I decided I knew enough about Windows for now and needed to branch out.

Or maybe I was just in a rut.

Well, I'm happy about whatever it was that caused me to make my latest impulse purchase.

Haskell program in Aquamacs

I've had it less than a week. Great stuff so far:

  • Having a unix-based OS to develop on is fantastic. The compilers for the languages I prefer to use in my spare time these days seem to work much better, presumably because many of the people who work on those languages also use Macs. GHC, for one, isn't terribly well supported on Windows.
  • It only took me a second to figure out how to swap caps lock and control for emacs.
  • It's great to have a terminal that doesn't suck. (Though, it'd be hard to do worse than cmd.exe. I'm aware of several valiant attempts at replacing it, but none compare.)
  • GarageBand - watch for my band to make a splash on the indie hate folk scene sometime in 2007. That's a genre I just invented, by the way.

I guess that's mostly developer stuff, which is odd since Apple's marketing actively makes fun of programmers.

I don't see myself ceasing Windows development anytime in the next decade, though, so if you're only here for .NET debugging stuff I wouldn't head for the hills just yet.