Reading the description of #27 I realized that race (and actually, concurrently) argument order is not arbitrary.
I was "naively" thinking that in the context of an async exception received by the race thread, it would be propagated at the same time to both arguments of race. However, it is first propagated to the "right" one, then to the "left" one (once the "right" thread had terminated). The example with withAsync in the documentation is clear about that, but implicit.
This can lead to delay in the shutdown of the "left" thread AND even "infinite" lock if the "right" thread is stuck in an uninterruptible task and actually waits for the left thread "signal" to stop.
For example, the following piece of code:
stopJob <- newIORef False
let
actionA = do
someInterruptibleOperation `finally` writeIORef stopJob True
actionB = do
-- It is not interruptible (e.g. FFI call), but will cooperatively stop when stopJob value is turned to True
someNotInterruptibleAction stopJob
race actionA actionB
In this context, when the main thread will receive an async exception, it will be first sent to actionB, which will never stop and hence actionA will never get the message and will hence never stop actionB. Using race actionB actionA instead solves this part of the problem.
Note that race actionB actionA is not safe either because actionA can be interrupted immediately before the exception handler is set and as a result actionB will never be see the stopJob value to True. You have to ensure that the exception handler in actionA is setup before actionB reachs the ininterruptible state.
Reading the description of #27 I realized that
race(and actually,concurrently) argument order is not arbitrary.I was "naively" thinking that in the context of an async exception received by the
racethread, it would be propagated at the same time to both arguments ofrace. However, it is first propagated to the "right" one, then to the "left" one (once the "right" thread had terminated). The example withwithAsyncin the documentation is clear about that, but implicit.This can lead to delay in the shutdown of the "left" thread AND even "infinite" lock if the "right" thread is stuck in an uninterruptible task and actually waits for the left thread "signal" to stop.
For example, the following piece of code:
In this context, when the main thread will receive an async exception, it will be first sent to
actionB, which will never stop and henceactionAwill never get the message and will hence never stop actionB. Usingrace actionB actionAinstead solves this part of the problem.Note that
race actionB actionAis not safe either becauseactionAcan be interrupted immediately before the exception handler is set and as a resultactionBwill never be see thestopJobvalue toTrue. You have to ensure that the exception handler in actionA is setup before actionB reachs the ininterruptible state.