[Haskell-cafe] Is Data.Pool not thread-safe or have I done something silly?

Cody Gman cody at codygman.dev
Sat Mar 13 01:09:03 UTC 2021


I wrote some code with a Data.Pool that has 1 stripe, 1 max resource, and then basically did `Async.replicateConcurrently_ . withResource $ \res -> f res`.

I expect withResource to block in each of those threads until the first thread spawned is done with that Resource and releases.

To be clear, I get output like:

```shell
/tmp $ stack exec --resolver lts-17.5 --package resource-pool --package stm --package hslogger --package time --package concurrent-output -- ghc -O2 -threaded -fwarn-unused-imports testpool.hs && ./testpool
acquire 0 - .81428
acquire 1 - .81431
acquire 2 - .81438
acquire 3 - .81440
acquire 4 - .81448
ThreadId 8:  processing 1 - .82460
ThreadId 17:  processing 4 - .82461
ThreadId 11:  processing 2 - .82464
ThreadId 14:  processing 3 - .82464
ThreadId 5:  processing 0 - .82465
anything else?
release 4 - .14427
release 3 - .14430
release 2 - .14431
release 1 - .14431
release 0 - .14432
anything else?
anything else?

```

I expect output like:

```shell
/tmp $ stack exec --resolver lts-17.5 --package resource-pool --package stm --package hslogger --package time --package concurrent-output -- ghc -O2 -threaded -fwarn-unused-imports testpool.hs && ./testpool
[1 of 1] Compiling Main             ( testpool.hs, testpool.o )
Linking testpool ...
acquire 0
ThreadId 5:  processing 0 - .01129
release 0
acquire 1 -- I thought this would have blocked until 0 was released
ThreadId 8:  processing 1 - .01120
release 1
acquire 2
ThreadId 11:  processing 2 - .01123
release 2
acquire 3
ThreadId 14:  processing 3 - .01129
release 3
acquire 4
ThreadId 17:  processing 4 - .01129
release 4
anything else?
anything else?
anything else?
```

Here is the code:

```
#!/usr/bin/env stack
-- stack script --resolver lts-17.5 --package resource-pool --package stm --package hslogger --package time --package concurrent-output

import Control.Concurrent
import Control.Concurrent.Async
import Control.Concurrent.STM
import Data.Pool
import Data.Time
import System.Console.Concurrent
import System.Clock

main :: IO ()
main = do
  counter <- newTVarIO 0
  let acquire = do
        k <- atomically $ do
          k <- readTVar counter
          writeTVar counter (k + 1)
          return k
        now <- getTime Monotonic
        outputConcurrent ("acquire " ++ show k ++ " - "++ show now ++ "\n")
        return k
      release k = do
        now <- getTime Monotonic
        outputConcurrent ("acquire " ++ show k ++ " - "++ show now ++ "\n")

  withConcurrentOutput $ do
    -- create a pool that only allows 1 resource
    pool <- createPool acquire release 1 500 1
    replicateConcurrently_ 5 $ do
      useResourceFor (seconds 10) pool

  -- Why do you need these to see the release messages?
  putStrLn "anything else?" >> threadDelay (seconds 5)
  putStrLn "anything else?" >> threadDelay (seconds 5)
  putStrLn "anything else?" >> threadDelay (seconds 5)

useResourceFor waitSeconds pool = withResource pool $ \i -> do
  threadDelay waitSeconds
  tid <- myThreadId
  now <- getTime Monotonic
  outputConcurrent $ show tid <> ": " <> " processing " <> show i <> " - " <> show now <> "\n"

seconds = (* 1000000)
```

I'm hoping someone could help explain what I did wrong or confirm it's a bug.

Thanks,

Cody


More information about the Haskell-Cafe mailing list