`closeRegistry` does not cancel a thread forked during close before it marks the registry closed
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 46/100
Direzione di ricerca
Start with unsafeReleaseAllBy in resource-registry/src/Control/ResourceRegistry.hs:680 and trace how registryReleaseThreads is read, how the registry is marked closed, and how linked threads are cancelled. Use the reproduction in the issue to check the late-fork race against the early control mode. Done means shutdown reliably cancels a thread forked during close without producing RegistryClosedException or ExceptionInLinkedThread.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
unsafeReleaseAllBy (resource-registry/src/Control/ResourceRegistry.hs:680) runs three steps:
- Read
registryReleaseThreadsand cancel those threads. - Mark the registry closed.
- Release all resources that are left.
If a thread forks a new thread between step 1 and step 2, step 1 does not cancel the new thread.
The new thread then runs on a closed registry until step 3.
If it calls allocate in that time, it gets RegistryClosedException.
Example
withRegistry $ \rr -> do -- "main" thread
_ <- forkThread rr "acceptor" $ forever $ do -- "acceptor" thread
conn <- accept
forkLinkedThread rr "handler" $ do -- one "handler" thread per connection
buf <- allocate rr newBuffer freeBuffer
serve conn buf
waitForShutdown
-- when "main" leaves withRegistry, it calls closeRegistry
If a connection arrives during shutdown, the steps can run in this order:
main step 1: read registryReleaseThreads = {acceptor}
acceptor accept returns, forkLinkedThread -> handler
main step 1: cancel acceptor
main step 2: registry closed
handler allocate rr ... -> RegistryClosedException
main step 3: cancel handler
The handler is linked, so "main" gets ExceptionInLinkedThread.
A normal shutdown then reports an error.
Reproduction
In each round, the "slow" thread is the newest thread, so step 1 cancels it first.
Its finally lets the acceptor fork the handler, which is after step 1 has read registryReleaseThreads.
In the control mode (early), the acceptor forks the handler before close starts.
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.ResourceRegistry
import Data.IORef
import System.Environment
-- Returns True if the handler got RegistryClosedException.
oneRound :: Bool -> IO Bool
oneRound late = do
hit <- newIORef False
go <- newEmptyMVar
started <- newEmptyMVar
withRegistry $ \rr -> do
let handler = forever $ do
r <- try (allocate rr (\_ -> pure ()) (\_ -> pure ()))
case r of
Left (_ :: RegistryClosedException) -> writeIORef hit True >> throwIO ThreadKilled
Right (k, ()) -> void (release k) >> threadDelay 10
_ <- forkThread rr "acceptor" $ do
when late $ takeMVar go
_ <- forkThread rr "handler" handler
putMVar started ()
waitForCancel
_ <- forkThread rr "slow" $
waitForCancel
`finally` (when late (putMVar go ()) >> threadDelay 1000)
unless late $ takeMVar started
threadDelay 1000
readIORef hit
-- Block until another thread cancels this one.
waitForCancel :: IO a
waitForCancel = forever (threadDelay 1000000)
main :: IO ()
main = do
[mode, n] <- getArgs
hits <- length . filter id <$> replicateM (read n) (oneRound (mode == "late"))
putStrLn $ mode <> ": " <> show hits <> " of " <> n <> " rounds saw RegistryClosedException"
The handler loop calls threadDelay 10 because the body of a forkThread thread runs MaskedInterruptible.
Without a blocking call, the cancel from closeRegistry cannot reach the handler.
Built with GHC 9.10.3, -O1 -threaded, and run with +RTS -N4:
late: 4 of 1000 rounds saw RegistryClosedException
early: 0 of 1000 rounds saw RegistryClosedException
The count for late changes from run to run. In three runs it was 4, 14, and 18.
- Lingua principale
- Haskell
- Stelle
- 2
- Fork
- 0
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di IntersectMBO/io-classes-extra
-
`unsafeReleaseAll` throws `ResourceRegistryUsedFromUntrackedThread` when called from an untracked threadForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 80/100
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 78/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 35/100
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
IntersectMBO/io-classes-extra#6 · 2 reazioni ·
Tutte le issue di IntersectMBO/io-classes-extra
Issue simili
-
Web.Scotty.Trans.Lazy.setHeader calls addHeader instead of setHeaderForse già presa @tusharad l’ha presa 1 giorno fa. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
scotty-web/scotty#436 ·
-
Broken links in the docsAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
input-output-hk/haskell.nix#2617 ·
-
easy first-contributor first-contributors good first issue haskell help wanted up-for-grabs
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 82/100
fork-commit-merge/fork-commit-merge#8455 ·
I maintainer di solito rispondono entro 1 giorno
-
Add cabal-gild as fixerApertanew tool
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
dense-analysis/ale#5176 ·
-
infrastructure
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
alunduil/siren-json.hs#278 ·
I maintainer di solito rispondono entro 1 giorno