Hi Michael,
Good work - what a lovely topic for your master's thesis! There's something a little bit odd about the real world being able to access the result of a transaction that hasn't yet (and may still yet not be) committed, but your arguments for why this is a good thing are compelling. It is also a bit odd that a STM transaction nested running within the finalizer won't see the results of the previous transaction as it's not yet committed. Not too surprising once you read your justification, but surprising beforehand.
A finalizer which has non-atomic real-world effects needs to be quite careful about undoing those effects when exceptions are thrown. For instance, looking at your example code[1]:
------------------------------------------------------------------------------
durably :: DatabaseHandle d -> TX d a -> IO a
durably h (TX m) = atomicallyWithIO action finalizer
where
action = runStateT m (database h, [])
finalizer (a, (_,ops)) = serialize ops h >> return a
serialize :: [Operation d] -> DatabaseHandle d -> IO ()
serialize ops (DatabaseHandle _ h) =
withMVar h (\h -> forM_ ops $ B.hPut h . runPut . safePut)
------------------------------------------------------------------------------[1]
https://github.com/mcschroeder/social-example/blob/8925056c/tx/TX.hs#L98
If some of those B.hPut calls succeed but then one fails (e.g. the disk is full) then the transaction will be rolled back, but the on-disk state will be left partially written. You're in good company with this problem, by the way - I've known large, expensive, commercial database products fail horribly and irretrievably in exactly this fashion when encountering a full disk at the wrong moment!
Even if the finalizer did include exception handling to deal with this situation, what happens with asynchronous exceptions? Does the finalizer run with async exceptions masked? I think it needs to: if not, then it seems it could run past the point where cleanup could occur and then receive an exception: essentially the finalizer runs to completion and then the STM transaction rolls back. Async exceptions get a brief mention in your thesis but I can't see anything about this point there - apologies if I've missed it.
Cheers,
David