Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[BUG] - Leaky thread in mkTimer #6083

Open
lehins opened this issue Jan 21, 2025 · 0 comments
Open

[BUG] - Leaky thread in mkTimer #6083

lehins opened this issue Jan 21, 2025 · 0 comments
Labels
needs triage Issue / PR needs to be triaged.

Comments

@lehins
Copy link
Contributor

lehins commented Jan 21, 2025

Internal

Area

Tracing

Summary

Function mkTimer creates a thread that is never killed. Consider switching to using async

mkTimer ioAction state callPeriodInS = do
callPeriod <- newTVarIO callPeriodInS
elapsedTime <- newTVarIO 0
isRunning <- newTVarIO state
void . forkIO $ forever $ do

Steps to reproduce
Here is an isolated example of how this thread creation in mkTimer is wrong:

#!/usr/bin/env cabal
{- cabal:
  build-depends:
    base, say
  ghc-options: -Wall -O1 -threaded
-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Concurrent
import Control.Monad
import Data.IORef
import GHC.Exts (fromString)
import Say

mkTimer ::
  IO ()
mkTimer = do
  nRef <- newIORef (0 :: Int)
  void . forkIO $ forever $ do
    threadDelay 1_000_000
    n <- atomicModifyIORef' nRef $ \i -> (i + 1, i)
    say $ "Forever waited for: " <> fromString (show n ++ " sec")

main :: IO ()
main = do
  tid <- forkIO mkTimer
  say $ "Started thread: " <> fromString (show tid)
  threadDelay 5_000_000
  say $ "Killing thread: " <> fromString (show tid)
  killThread tid
  say $ "Thread should be dead now: " <> fromString (show tid)
  say "Waiting for another 5 sec"
  threadDelay 5_000_000

Executing this script depicts that the thread created in mkTimer is inaccessible and will persist for the lifetime of the process, regardless if the thread that started it gets killed.

Executing this script will give you this output:

> ./timer.hs
Started thread: ThreadId 2
Forever waited for: 0 sec
Forever waited for: 1 sec
Forever waited for: 2 sec
Forever waited for: 3 sec
Killing thread: ThreadId 2
Thread should be dead now: ThreadId 2
Waiting for another 5 sec
Forever waited for: 4 sec
Forever waited for: 5 sec
Forever waited for: 6 sec
Forever waited for: 7 sec
Forever waited for: 8 sec

As you can see, killing of the thread spawned in main does not cleanup the thread that runs indeed forever

Expected behavior

Thread created in mkTimer should be cleaned up when thread that spawned it gets killed.

Another side note about Timer interface is that it unnecessarily uses TVars where IORef with atomicModifyIORef' would be a much better fit that resulted in less overhead

@lehins lehins added the needs triage Issue / PR needs to be triaged. label Jan 21, 2025
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
needs triage Issue / PR needs to be triaged.
Projects
None yet
Development

No branches or pull requests

1 participant