Avoiding BlockedIndefinitelyOnSTM exceptions

I have what may sound like an unusual request: I would like to automatically avoid `BlockedIndefinitelyOnSTM` exceptions with a primitive that looks something like this: safe :: STM a -> STM (Maybe a) This hypothetical `safe` primitive would attempt a transaction, and if `ghc` detects that this transaction would fail because of an `BlockedIndefinitelyOnSTM` exception it will return `Nothing` instead of throwing an uncatchable exception. I originally simulated a limited form of this behavior using `pipes-concurrency`. I instrumented the garbage collector (using weak references) to detect when an STM variable was garbage collected and to safely cancel any transactions that depended on those variables. You can see the implementation here: https://github.com/Gabriel439/Haskell-Pipes-Concurrency-Library/blob/23e7e2d... The original purpose behind this was to easily read and write to a channel without having to count references to the channel. I reasoned that the garbage collector *already knew* how many open references there were to channel, so I thought "why not use the garbage collector to gracefully cancel transactions that would block just before they would trigger the exception?" This worked really well up until ghc-7.8 changed something and the above trick no longer works. To be honest, I'm surprised that it ever worked at all, which is why I'm not requesting restoring the original behavior. Instead, I think providing something like the above `safe` primitive would be nicer, if possible. Would it be possible to implement something like `safe`? Alternatively, is it possible to make the `BlockedIndefinitelyOnSTM` exception catchable? P.S. I'm also interested in learning more about what may have caused the change in behavior in the transition from ghc-7.6 to ghc-7.8. What changes were made to the interaction between STM and weak references that may have triggered this?

Gabriel
Is the underlying issue one of “scope” - STM variables have global scope, would a batter approach to be to create scope of such things and then some overall recovery mechanism could handle such an exception within that scope?
Neil
On 14 Jul 2014, at 03:30, Gabriel Gonzalez
I have what may sound like an unusual request: I would like to automatically avoid `BlockedIndefinitelyOnSTM` exceptions with a primitive that looks something like this:
safe :: STM a -> STM (Maybe a)
This hypothetical `safe` primitive would attempt a transaction, and if `ghc` detects that this transaction would fail because of an `BlockedIndefinitelyOnSTM` exception it will return `Nothing` instead of throwing an uncatchable exception.
I originally simulated a limited form of this behavior using `pipes-concurrency`. I instrumented the garbage collector (using weak references) to detect when an STM variable was garbage collected and to safely cancel any transactions that depended on those variables. You can see the implementation here:
https://github.com/Gabriel439/Haskell-Pipes-Concurrency-Library/blob/23e7e2d...
The original purpose behind this was to easily read and write to a channel without having to count references to the channel. I reasoned that the garbage collector *already knew* how many open references there were to channel, so I thought "why not use the garbage collector to gracefully cancel transactions that would block just before they would trigger the exception?"
This worked really well up until ghc-7.8 changed something and the above trick no longer works. To be honest, I'm surprised that it ever worked at all, which is why I'm not requesting restoring the original behavior. Instead, I think providing something like the above `safe` primitive would be nicer, if possible.
Would it be possible to implement something like `safe`?
Alternatively, is it possible to make the `BlockedIndefinitelyOnSTM` exception catchable?
P.S. I'm also interested in learning more about what may have caused the change in behavior in the transition from ghc-7.6 to ghc-7.8. What changes were made to the interaction between STM and weak references that may have triggered this? _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

I don't quite understand your question, but I'll try to give a fuller explanation of the problem I was trying to solve to see if it perhaps answers your question. The motivation behind `pipes-concurrency` was to make it easy for readers and writers to coordinate implicitly (using the garbage collector) instead of explicitly. Here's an example of code that people would use `pipes-concurrency` to write: main = do (output, input) <- spawn Single a1 <- async $ runEffect $ someProducer >-> toOutput output a2 <- async $ runEffect $ fromInput input >-> someConsumer mapM_ wait [a1, a2] For people unfamiliar with `pipes` or `pipes-concurrency`, what the above code does is it `spawn`s a `TMVar` that you can read or write. Then it forks two threads, one of which feeds some values to the `TMVar` and the other of which reads some values from the `TMVar`. What was neat about the way `pipes-concurrency` works is that: * If `someProducer` produces fewer values than `someConsumer` requests from `TMVar`, then `someConsumer` would detect that and gracefully terminate instead of trying to read a value from the now-permanently-empty `TMVar`. * If `someConsumer` consumes fewer values than `someProducer` feeds to the `TMVar` then `someProducer` would detect that and gracefully terminate instead of trying to write a new value to the now-permanently-full `TMVar`. Note that throwing an exception to either of these threads is not a suitable substitute for graceful termination in more sophisticated examples. The rationale behind graceful termination is to allow running more logic in these threads after they are done reading or writing from the STM variable. My feeling is that the garbage collector already knows that these reads or writes are doomed, so why not make use of that knowledge to gracefully recover from doomed transactions? On 07/14/2014 12:19 AM, Neil Davies wrote:
Gabriel
Is the underlying issue one of “scope” - STM variables have global scope, would a batter approach to be to create scope of such things and then some overall recovery mechanism could handle such an exception within that scope?
Neil
On 14 Jul 2014, at 03:30, Gabriel Gonzalez
wrote: I have what may sound like an unusual request: I would like to automatically avoid `BlockedIndefinitelyOnSTM` exceptions with a primitive that looks something like this:
safe :: STM a -> STM (Maybe a)
This hypothetical `safe` primitive would attempt a transaction, and if `ghc` detects that this transaction would fail because of an `BlockedIndefinitelyOnSTM` exception it will return `Nothing` instead of throwing an uncatchable exception.
I originally simulated a limited form of this behavior using `pipes-concurrency`. I instrumented the garbage collector (using weak references) to detect when an STM variable was garbage collected and to safely cancel any transactions that depended on those variables. You can see the implementation here:
https://github.com/Gabriel439/Haskell-Pipes-Concurrency-Library/blob/23e7e2d...
The original purpose behind this was to easily read and write to a channel without having to count references to the channel. I reasoned that the garbage collector *already knew* how many open references there were to channel, so I thought "why not use the garbage collector to gracefully cancel transactions that would block just before they would trigger the exception?"
This worked really well up until ghc-7.8 changed something and the above trick no longer works. To be honest, I'm surprised that it ever worked at all, which is why I'm not requesting restoring the original behavior. Instead, I think providing something like the above `safe` primitive would be nicer, if possible.
Would it be possible to implement something like `safe`?
Alternatively, is it possible to make the `BlockedIndefinitelyOnSTM` exception catchable?
P.S. I'm also interested in learning more about what may have caused the change in behavior in the transition from ghc-7.6 to ghc-7.8. What changes were made to the interaction between STM and weak references that may have triggered this? _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On Mon, Jul 14, 2014 at 11:16 AM, Gabriel Gonzalez
I don't quite understand your question, but I'll try to give a fuller explanation of the problem I was trying to solve to see if it perhaps answers your question.
I think the suggestion was something like this: -- | The @s@ type is an abstract "heap id" just like in 'ST'. type STSTM s a -- | The second argument allows handling 'BlockedIndefinitelyOnSTM' etc. runSTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> b The idea being that, if the problem with catching BlockedIndefinitelyOnSTM has to do with the fact that all STM variables have global scope and so even if we could catch the exception itself we'd still have problems with cleaning up the collateral damage[1], then that's why it doesn't make sense to allow BlockedIndefinitelyOnSTM to be caught. However, by using an abstract heap-id we can bundle all related STSTM variables together, and we know that once runSTSTM exits they can't be accessed from anywhere else— so even if there's an error, we can still clean up all the variables, threads, and other jetsam associated with this STSTM computation. [1] I don't know if this is actually the reason why why BlockedIndefinitelyOnSTM is uncatchable, rather it sounded like this is what Neil Davies was suggesting to be the reason. Also, I do seem to recall something like this actually being the case; though it's unclear whether the STSTM approach would actually be able to solve the problem. -- Live well, ~wren

On Sat, Jul 19, 2014 at 11:24 PM, wren romano
-- | The second argument allows handling 'BlockedIndefinitelyOnSTM' etc. runSTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> b
That should've been something more sensible, like: atomicallySTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> IO (Either a b)
The idea being that, if the problem with catching BlockedIndefinitelyOnSTM has to do with the fact that all STM variables have global scope and so even if we could catch the exception itself we'd still have problems with cleaning up the collateral damage[1], then that's why it doesn't make sense to allow BlockedIndefinitelyOnSTM to be caught.
[1] I don't know if this is actually the reason why why BlockedIndefinitelyOnSTM is uncatchable, rather it sounded like this is what Neil Davies was suggesting to be the reason. Also, I do seem to recall something like this actually being the case; though it's unclear whether the STSTM approach would actually be able to solve the problem.
Fwiw, after (re)reading ezyang's blog post about BlockedIndefinitelyOnSTM, it seems clear that this is not actually what the problem is. Though he also mentioned that the exact details of BlockedIndefinitelyOnSTM shouldn't be relied upon, since the exception is just a trick to kill off useless threads in order to collect more garbage. ... In terms of solving your actual problem in pipes, it seems like what you really want are weak-references for STM. That way you don't rely on the behavior of BlockedIndefinitelyOnSTM, and you can still get garbage collection to send you signals whenever something becomes inaccessible, allowing you to handle things however you like. Seems like it shouldn't be too hard to do, since weak-references for IO are already supported; though, of course, it'll still take a fair deal of hacking to implement it. -- Live well, ~wren

Wren
That is sort of idea I was alluding to - there is a communication scope, the set
of things that now need be cleaned up.
In designing systems I'm always aggregating scope of failures to create
“units of redundancy” and handling / recovering (and making sure failure
within that scope becomes “total”) - reduces the complexity of reasoning
about fault modes.
Neil
On 20 Jul 2014, at 04:48, wren romano
On Sat, Jul 19, 2014 at 11:24 PM, wren romano
wrote: -- | The second argument allows handling 'BlockedIndefinitelyOnSTM' etc. runSTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> b
That should've been something more sensible, like:
atomicallySTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> IO (Either a b)
The idea being that, if the problem with catching BlockedIndefinitelyOnSTM has to do with the fact that all STM variables have global scope and so even if we could catch the exception itself we'd still have problems with cleaning up the collateral damage[1], then that's why it doesn't make sense to allow BlockedIndefinitelyOnSTM to be caught.
[1] I don't know if this is actually the reason why why BlockedIndefinitelyOnSTM is uncatchable, rather it sounded like this is what Neil Davies was suggesting to be the reason. Also, I do seem to recall something like this actually being the case; though it's unclear whether the STSTM approach would actually be able to solve the problem.
Fwiw, after (re)reading ezyang's blog post about BlockedIndefinitelyOnSTM, it seems clear that this is not actually what the problem is. Though he also mentioned that the exact details of BlockedIndefinitelyOnSTM shouldn't be relied upon, since the exception is just a trick to kill off useless threads in order to collect more garbage.
...
In terms of solving your actual problem in pipes, it seems like what you really want are weak-references for STM. That way you don't rely on the behavior of BlockedIndefinitelyOnSTM, and you can still get garbage collection to send you signals whenever something becomes inaccessible, allowing you to handle things however you like. Seems like it shouldn't be too hard to do, since weak-references for IO are already supported; though, of course, it'll still take a fair deal of hacking to implement it.
-- Live well, ~wren _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On Sun, Jul 20, 2014 at 5:48 AM, wren romano
On Sat, Jul 19, 2014 at 11:24 PM, wren romano
wrote: -- | The second argument allows handling 'BlockedIndefinitelyOnSTM'
etc.
runSTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> b
That should've been something more sensible, like:
atomicallySTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> IO (Either a b)
Wouldn't combining it wih ST defeat the purpose of STM? The purpose of STM being to synchronize access to shared variables using atomic transactions. If all the variables in your atomic transaction are guaranteed to be local and not touched by any other transaction, which I believe the above type says, then you don't need a transaction at all. Or was the idea to create some kind of outer scope within which individual atomic (sub)transactions may share access to variables, but not without?
The idea being that, if the problem with catching BlockedIndefinitelyOnSTM has to do with the fact that all STM variables have global scope and so even if we could catch the exception itself we'd still have problems with cleaning up the collateral damage[1], then that's why it doesn't make sense to allow BlockedIndefinitelyOnSTM to be caught.
[1] I don't know if this is actually the reason why why BlockedIndefinitelyOnSTM is uncatchable, rather it sounded like this is what Neil Davies was suggesting to be the reason. Also, I do seem to recall something like this actually being the case; though it's unclear whether the STSTM approach would actually be able to solve the problem.
Fwiw, after (re)reading ezyang's blog post about BlockedIndefinitelyOnSTM, it seems clear that this is not actually what the problem is. Though he also mentioned that the exact details of BlockedIndefinitelyOnSTM shouldn't be relied upon, since the exception is just a trick to kill off useless threads in order to collect more garbage.
...
In terms of solving your actual problem in pipes, it seems like what you really want are weak-references for STM. That way you don't rely on the behavior of BlockedIndefinitelyOnSTM, and you can still get garbage collection to send you signals whenever something becomes inaccessible, allowing you to handle things however you like. Seems like it shouldn't be too hard to do, since weak-references for IO are already supported; though, of course, it'll still take a fair deal of hacking to implement it.
-- Live well, ~wren _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On Sun, Jul 20, 2014 at 5:22 PM, Gábor Lehel
On Sun, Jul 20, 2014 at 5:48 AM, wren romano
wrote: On Sat, Jul 19, 2014 at 11:24 PM, wren romano
wrote: -- | The second argument allows handling 'BlockedIndefinitelyOnSTM' etc. runSTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> b
That should've been something more sensible, like:
atomicallySTSTM :: (forall s. STSTM s a) -> (STMError -> b) -> IO (Either a b)
Yeah, that's not quite what I meant either. Will have to think about it more.
Wouldn't combining it wih ST defeat the purpose of STM? The purpose of STM being to synchronize access to shared variables using atomic transactions. If all the variables in your atomic transaction are guaranteed to be local and not touched by any other transaction, which I believe the above type says, then you don't need a transaction at all.
Or was the idea to create some kind of outer scope within which individual atomic (sub)transactions may share access to variables, but not without?
Yeah, the idea is to create smaller scopes to collect all the related references into a single heap/region, isolating them from other heaps, allowing the heap to be gc'ed in one go, etc. Arranging local scopes is generally a good idea since global scope is non-compositional. STM just says that heap interactions are transactional, it doesn't say there can be only one heap/region. Indeed, there can be performance benefits for adding regions to thread-based parallelism like STM. Since threads accessing disjoint regions cannot interfere with one another we have very strong guarantees about their data-flow and synchronization properties. This, in turn, can be used by the runtime to reduce memory traffic: associate each heap with a core, distributed as evenly as possible over the cores, and schedule all the threads over a given heap to be run on the core for that heap (or on "nearby" cores). Similarly there can be runtime performance benefits since we can use tricks like card marking to quickly check whether any other thread has modified our region; if not, then commit immediately; if so, then do the expensive check of running through the STM log to see if there's really a conflict. -- Live well, ~wren
participants (4)
-
Gabriel Gonzalez
-
Gábor Lehel
-
Neil Davies
-
wren romano