When a command inside a transaction times out the caller may roll back to a savepoint and try again. This works OK the first time. The second time it appears to work, but the rollback takes a long time. On the third attempt the command returns an empty result set. The attached console app reproduces the problem.

On investigation, it appears that Npgsql is not cancelling any command after the first, once NpgsqlConnector.CancelRequestCalled is true.

The first timeout is processed correctly - the command is cancelled. The client then rolls back to the savepoint and tries the command again:
2013-02-21 23:14:27 EST LOG: statement: ROLLBACK TO SAVEPOINT ExecuteScalarWithRetry
2013-02-21 23:14:27 EST LOG: statement: SELECT 'Attempt 2' FROM pg_sleep(((3)))

The command again times out from the client's point of view, but Npgsql does not send another "cancel request", so it actually continues exucuting on the server. The client, blissfully unaware of this, sends a rollback command:
2013-02-21 23:14:30 EST LOG: statement: ROLLBACK TO SAVEPOINT ExecuteScalarWithRetry

That rollback command takes an unusually long time. When the server returns it actually returns the response for the previous command ("SELECT 'Attempt 2'"), but the client thinks it's for the rollback and ignores it.

The client then tries the command a third time:
2013-02-21 23:14:30 EST LOG: statement: SELECT 'Attempt 3' FROM pg_sleep(((3)))

The server immediately returns a response, but it's the response for the previous rollback command, which the client doesn't expect.