Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

`closeRegistry` does not cancel a thread forked during close before it marks the registry closed

オープン
#25 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

@dnadales がすでに取り組んでいます。

2026年10月27日 から。

  • #27 @dnadales による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
46/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
haskell
領域
backend

調査の方向性

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:

  1. Read registryReleaseThreads and cancel those threads.
  2. Mark the registry closed.
  3. 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 を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

IntersectMBO/io-classes-extra のほかの issue

IntersectMBO/io-classes-extra の issue をすべて見る

似ている issue

Haskell の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。