-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
auto-update: mkDebounce leaking the thread #980
Comments
I wonder how this could be fixed. I guess Or create a function that captures its own MVar and doesn't need to fork a thread? mkDebounce :: IO () -> IO (IO ())
mkDebounce action = do
baton <- newMVar ()
pure $ do
mBaton <- tryTakeMVar baton
case mBaton of
Nothing -> pure ()
Just () -> do
-- handle Leading/Trailing
-- and `putMVar ()` when done with action + delay @kazu-yamamoto would this idea be an ok replacement for |
This module was created by @snoyberg. |
Will try to reproduce with my PR #996 at the earliest convenience. |
Note that the recent GHC has |
I'll add |
See updateThreadName. |
There is a high change that the issue is actually caused by The reproducer in the issue description is probably wrong, it's not letting the threads chance to receive |
|
I've ran OP's example on And I've ran it with my PR #996 on GHC 9.6.6, which used a lot less memory, and also found that when I added a print statement in the debounce action, the printing would stagger and lag with the current code, but with my PR it was a lot smoother and just flooded my screen as fast as possible, instead of having hiccups and freezes. Average results from trying the OP's example
|
Now some tests with The The #996 way of doing debouncing still has 11000~ish threads open after all that, and this goes down by a bit with a 1s This might have to do with the GC taking longer to recognize the threads can be cleaned up? Or something like that? I honestly don't have too much knowledge about the GHC runtime to know how it cleans up green threads, and how accurate Source code that was run:{-# LANGUAGE NumericUnderscores #-}
import Control.Concurrent (threadDelay)
import Control.Monad (forM_)
import GHC.Conc (listThreads)
import System.Environment (getArgs)
import System.Mem (performMajorGC)
import Control.Debounce (
debounceAction,
defaultDebounceSettings,
mkDebounce,
)
main :: IO ()
main = do
args <- getArgs
let action =
case args of
[] -> pure ()
"0" : _ -> pure ()
i : _ -> threadDelay $ read i * 1_000_000
forM_ [(0::Int) .. 100_000] $ \_ -> do
r <- mkDebounce defaultDebounceSettings{debounceAction = action}
r
nowThreads <- length <$> listThreads
threadDelay 5_000_000
laterThreads <- length <$> listThreads
threadDelay 1_000_000
performMajorGC
gcThreads <- length <$> listThreads
threadDelay 1_000_000
putStr "\nThreads amount right after: " >> print nowThreads
putStr "\nThreads amount 5 sec later: " >> print laterThreads
putStr "\nThreads amount after GC: " >> print gcThreads
putStrLn "" Addendum 1
Addendum 2
|
Ah, after adding some #996 Executable
And this is the result on the master Executable
So it does seem the new way of debouncing actually cleans up the threads, whereas the current one leaks at least somewhat? Some extra testing1 sec leaves less: `master` branch 1s `threadDelay` action
2 sec leaves even less: `master` branch 2s `threadDelay` action
But 4 sec leaves more?: `master` branch 4s `threadDelay` action
|
mkDebounce
internally forks a thread, but never kills it. To reproduce, run the following with+RTS -s -RTS
and observe memory usage.Output:
132MiB memory is too much for the above program.
As a possible solution, consider attaching a finalizer to the debounced action that will kill the thread. I'd be happy to prepare a patch if you agree with such a solution.
Other possibility is to use a closable channel instead of
MVar
. I didn't try it though.A bit of context: fast-logger uses
mkDebounce
to flush logs, so each logger comes with a thread which never exits.Thanks!
The text was updated successfully, but these errors were encountered: