`closeRegistry` does not cancel a thread forked during close before it marks the registry closed
評価
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
説明
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.
- 主要言語
- Haskell
- スター
- 2
- フォーク
- 0
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
IntersectMBO/io-classes-extra のほかの issue
-
`unsafeReleaseAll` throws `ResourceRegistryUsedFromUntrackedThread` when called from an untracked thread対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
-
難易度 1/5 1時間未満 初心者へのやさしさ 78/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 35/100
-
難易度 3/5 1〜2日 初心者へのやさしさ 45/100
IntersectMBO/io-classes-extra#6 · リアクション 2 件 ·
IntersectMBO/io-classes-extra の issue をすべて見る
似ている issue
-
newcomers
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
JacquesCarette/Drasil#5591 ·
メンテナーはふだん 4 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
-
bug
難易度 1/5 1時間未満 初心者へのやさしさ 86/100
メンテナーはふだん 1 日以内に返信
-
Vim-mode indent crashes: monaco-vim's browser build inlines monaco-editor 0.33対応中かも @x-labb が 1 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
compiler-explorer/compiler-explorer#9402 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
メンテナーはふだん 1 日以内に返信