yunomuのブログ

酒とゲームと上から目線

recv: invalid argument (Bad file descriptor)

この前(Conduit + Attoparsec (+ Concurrent) - yunomuのブログ)の記事に書いてあるコードを実行すると6〜8割くらいの確率でエラーが出ます。結構な確率で。

recv: invalid argument (Bad file descriptor)

こんなの。

recvとかfile descriptorとかそういうカーネルコールレベルの事を言われても困るというか。
なんとなく、リソース管理まわりの処理がうまくいってないのはわかるけども。

で、ググったらSnoymanさんが説明してくれていた。
sendWaiResponse with ResponseSource constructor causes "recv: invalid argument (Bad file descriptor)" error: Google Groups

要するに、前回はcurlをこんな感じで実装していました。

curl :: (MonadUnsafeIO m,
         MonadThrow m,
         MonadIO m,
         MonadBaseControl IO m,
         Failure HttpException m) =>
    String -> m (ResumableSource (ResourceT m) ByteString)
curl url = do
    request <- parseUrl url
    withManager $ \manager ->
        responseBody <$> http request manager

で、これをこんな風に使ってた。

main = runResourceT $ do
    liftIO $ curl "http://localhost/"

これの、どうも"withManager"がいけなかったらしい。

withManagerはManagerの確保と開放とあと例外処理とかなんとかめんどくさい部分をうまくやってくれるんですが、この場合は同じくリソースの確保開放例外処理などをうまくやってくれるはずのResourceTとかrunResourceTが絡んでいるのでどうもややこしい話になっているようで。おおかた既に閉じたファイルディスクリプタを律儀にというか厳密に閉じようとして失敗したとかそういう話なんでしょう。いや間違えて変なタイミングで閉じてるという線もあるけど。

つまりwithManager(とかcloseManager)を使わずに、ResourceTを使うなら開放処理は全部ResourceTに任せてしまえばいいという話です。
その方針で書き直すとこんな感じになります。

curl :: (MonadBaseControl IO m, MonadResource m) =>
    String -> m (ResumableSource m ByteString)
curl url = do
    request <- liftIO $ parseUrl url
    manager <- liftIO $ newManager def
    responseBody <$> http request manager

main :: IO ()
main = runResourceT $ do
    str <- curl "http://localhost/"
    ...

なんか副次的(?)にクラス制約と型定義が短くなった。
というか前から型定義のResourceTは気に食わなかったのよねぇ。消えてよかった。

教訓は、ResourceTを使う時はwith系の扱いに注意しましょうという感じでしょうか。
Yesod本(Developing Web Applications with Haskell and Yesod - O'Reilly Media: conduit-0.2準拠)には順を追って書いてあるのでwithとの関係はわかるけど競合関係とかはわかんないんですよね。というか今や既にconduit-0.5ですし。