jwiegley / async-pool

Other
21 stars 13 forks source link

makeDependent on already started tasks #1

Open redneb opened 10 years ago

redneb commented 10 years ago

I find makeDependent a little bit confusing: what happens if it is called in order to indicate that a particular task has dependencies when that task has already been started? For example

main :: IO ()
main = do
    p <- createPool
    withTaskGroupIn p 2 $ \g -> do
        t1 <- async g task1
        t2 <- async g task2
        threadDelay 1000 -- wait 1ms for task2 to start
        atomically $
            makeDependent p (taskHandle t2) (taskHandle t1)
        wait t1
        wait t2

for some task1, task2 :: IO ().

jwiegley commented 10 years ago

Hi @redneb,

There are several scenarios when makeDependent could be called in such a case:

  1. The parent task had not yet begun, but the child task had begun. In this case, the call to makeDependent achieves nothing. This is why you are encouraged to use asyncAfter, or asyncSTM and makeDependent in the same STM transaction.
  2. Neither task had begun. This establishes the dependency as normal.
  3. The parent had begun, but the child had not yet begun. This establishes the dependency as normal.
  4. Both tasks had already begun. This is just like #1 above.
  5. One of the tasks already finished. This is rather like #1 as well.
  6. Both tasks had finished. This makes the call a no-op.

John

redneb commented 10 years ago

Right. So I guess the point of this ticket was that (a) makeDependent should be removed from the public api in favor of asyncSTM/asyncAfter (and possibly other additions if necessary), or (b) the documentation should be updated to indicate that sometimes makeDependent is a no-op and its use should be discouraged. In general, I feel that makeDependent in not particularly elegant and it does not fit well with the rest of the module which is nicely designed.

jwiegley commented 10 years ago

@redneb I see your point. The problem with asyncAfter is that it dwells in IO, and so establishes only a 1-to-1 dependency without race. I included asyncSTM and makeDependent so that you could express arbitrary n-to-m dependencies within the scope of a single transaction, eliminating races. It should be clear from the types that only STM can give you reasonable results, but I will document this fact about makeDependent.

The other possibility is to add asyncAfterSTM, and drop makeDependent, and have both asyncAfter functions accept a list of Async handles.

The problem there is that I want to leave open the flexibility that one part of a user's application is responsible for submitting tasks, and another part (also in the STM transaction) is responsible for associating them. But I could certainly be placed in an "advanced" section of the documentation, and marked out with caveats.