haskellstm

Why is a TMVar populated in a function with putTMVar not visible from the calling function?


In the following code I am sending a record with an IO (TMVar o) off to get populated in a function then reading what I believe should be the same TMVar when the function returns. The problem is when I read it back it's empty and the application just blocks.

Why is this happening?

The Hook is intialised as follows:

hook = Hook { 
  -- other props
  hookResult = newEmptyTMVarIO,
 }

later in the ap:

executeHook :: (Text -> IO ()) -> Node i o -> IO ()
executeHook db =
  \case
    Fixture {} -> pure ()
    Hook
      { hookParent,
        hookStatus,
        hook,
        hookResult, -- IO (TMVar o)
        hookChildren
      } -> do
        input <- db "CALL PARENT LOCK EXECUTE HOOK" >> lockExecuteHook db hookParent
        result <- hook input
        hr <- hookResult
        mtb <- atomically $ isEmptyTMVar hr
        db $ "HOOK RESULT PUT EMPTY BEFORE: " <> txt mtb 
        atomically $ putTMVar hr result -- writes hook result to the TMVar
        mt <- atomically $ isEmptyTMVar hr
        db $ "HOOK RESULT PUT EMPTY AFTER: " <> txt mt 

lockExecuteHook :: (Text -> IO ()) -> Either o (Node i o) -> IO o
lockExecuteHook db parent =
  eitherf
    parent
    (\o -> db "NO PARENT HOOK RETURNING VALUE" >> pure o)
    ( \case
        Fixture {} -> pure ()
        hk@Hook
          { hookParent,
            hookStatus,
            hookResult, -- IO (TMVar o)
            hook,
            hookChildren,
            hookRelease
          } -> do
            bs <- hookStatus
            wantLaunch <- atomically $ tryLock db bs
            db $ "HOOK LOCK >>> " <> txt wantLaunch
            when
              wantLaunch
              $ executeHook db hk --  this writes hook result to the TMVar
            hr <- hookResult
            mt <- atomically $ isEmptyTMVar hr
            db $ "READING HOOK !!!!!!!!!!!!!!!!!!!!!!!!!! EMPTY: " <> txt mt
            r <- atomically $ readTMVar hr
            db "RETURNING FROM LOCK EXECUTE HOOK " >> pure r
    )

Debug (db) output

CALL PARENT LOCK EXECUTE HOOK
HOOK LOCK >>> True
HOOK RESULT PUT EMPTY BEFORE: True
HOOK RESULT PUT EMPTY AFTER: False
READING HOOK !!!!!!!!!!!!!!!!!!!!!!!!!! EMPTY: True

Solution

  • As pointed out in the above comments by @FyodorSoikin, @DanielWagner and @chi, the reason this was not working as expected is that hookResult was not initialised correctly.

    Setting the record field to an IO (TMVar a) will result in a new TMVar a every time hookResult is read, similar problem would occur if the field was set to an STM (TMVar a) using newTMVar. To make this work hookResult needs to be set to a TMVar a from within the IO or STM context.

    eg :: IO (Hook a)
    eg = do
           v <- newEmptyTMVarIO
           pure $ Hook { 
             -- other props
             hookResult = v,
           }