packages feed

hpgsql-0.1.0.0: src/Hpgsql/Locking.hs

module Hpgsql.Locking (getMyWeakThreadId, withMutex, Mutex, WeakThreadId) where

import Control.Concurrent (mkWeakThreadId, myThreadId)
import qualified Control.Concurrent.STM as STM
import Control.Exception.Safe (bracket)
import Control.Monad (void)
import Hpgsql.InternalTypes (Mutex (..), WeakThreadId (..), throwIrrecoverableError)
#if MIN_VERSION_base(4,19,0)
import GHC.Conc.Sync (fromThreadId)
#else
import GHC.Conc.Sync (showThreadId)
#endif

getMyWeakThreadId :: IO WeakThreadId
getMyWeakThreadId = do
  -- We don't keep a reference to `ThreadId` as it can stop threads from getting
  -- runtime exceptions and can prevent dead threads from being garbage-collected.
  -- It's explained somewhere in hackage.
  tid <- myThreadId
  wtid <- mkWeakThreadId tid
#if MIN_VERSION_base(4,19,0)
  pure $ WeakThreadId wtid (fromThreadId tid)
#else
  let tidStr = showThreadId tid
  pure $ WeakThreadId wtid tidStr
#endif

withMutex ::
  Mutex ->
  IO a ->
  IO a
withMutex (Mutex blockedByT) f = do
  thisThreadId <- getMyWeakThreadId
  bracket
    ( STM.atomically $ do
        blockedBy <- STM.readTVar blockedByT
        newSt :: (WeakThreadId, Int) <- case blockedBy of
          Nothing -> pure {- traceShowWith ("Grabbing ",) $ -} (thisThreadId, 1)
          Just (tid, nGrabs) ->
            if tid == thisThreadId
              then pure {- traceShowWith ("Grabbing ",) $ -} (thisThreadId, nGrabs + 1)
              else STM.retry
        STM.writeTVar blockedByT (Just newSt)
    )
    -- Release lock on success or error
    ( const $ void $ STM.atomically $ do
        blockedBy <- STM.readTVar blockedByT
        newSt <- case blockedBy of
          Nothing -> throwIrrecoverableError "Impossible: should have been blocked but was not!"
          Just (tid, nGrabs) ->
            let newLockState = {- traceShowWith ("Releasing ",) $ -} if nGrabs <= 1 then Nothing else Just (thisThreadId, nGrabs - 1)
             in if tid == thisThreadId then pure newLockState else throwIrrecoverableError "Impossible: Lock of a different thread!"
        STM.writeTVar blockedByT newSt
        -- debugPrint "Internal state: [Released control-msg-lock]."
    )
    $ \() -> f