Ah, of course you're right, I forgot that masking /= completely uninterruptible. And yes, it does seem like the finalizer needs to be masked by default, in order to prevent the kind of race condition you describe. Should be an easy change. Thanks for pointing that out!On 31 July 2015 at 15:06, David Turner <dct25-561bs@mythic-beasts.com> wrote:Hi Michael,I wholeheartedly agreed that timeout n $ atomicallyWithIO ... is important, but that would generally work even if the finalizer ran masked wouldn't it? Most actions that block are interruptible.Put another way, I'd be interested to see how you could make serialize safe without this. Something like this:serialize :: [Operation d] -> DatabaseHandle d -> IO ()serialize ops (DatabaseHandle _ h) =withMVar h (\h -> forM_ ops $ B.hPut h . runPut . safePut)`onException`magicalFileRollback h(ignoring the tedious implementation of magicalFileRollback) doesn't look quite right as if you got an async exception in the tiny window between onException exiting and serialize exiting then the STM transaction would roll back but the file wouldn't. I'm not convinced I totally understand async exceptions so if you (or anyone else) can point out why this works I'd be very grateful.Cheers,DavidOn 31 July 2015 at 13:18, Michael Schröder <mc.schroeder@gmail.com> wrote:_______________________________________________Hi David, I appreciate your interest in my thesis!A finalizer which has non-atomic real-world effects needs to be quite careful about undoing those effects when exceptions are thrown. [...] 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.Yes, you are absolutely right! The example application lacks some of the safeguards one would expect in a production-ready system. It was intended to be more of a demonstration of how easily one can in principle build a database-like system on top of STM using finalizers. It still requires some engineering effort to make it entirely safe and practical. I should have documented this better—or just gone the extra mile and actually made it safe!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?The finalizer does not run with async exceptions masked and you're right that one needs to be careful about how to deal with side-effects & async exceptions & cleanup within the finalizer—just like with any kind of I/O, really. In the TX example code, serialize should probably use withMVarMasked instead of withMVar. But I don't think the finalizer should run with async exceptions masked by default. There are uses cases where you really do want the finalizer to be interruptible from another thread, e.g. if you want to be able to timeout the whole transaction (STM part + finalizer part, as in: timeout n $ atomicallyWithIO ...)
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe