Haskell FFI: ForeignPtr, похоже, не освобождается (может быть, ошибка GHC?) - PullRequest
2 голосов
/ 20 июня 2009

Рассмотрим следующий фрагмент кода

import qualified Foreign.Concurrent
import Foreign.Ptr (nullPtr)

main :: IO ()
main = do
  putStrLn "start"
  a <- Foreign.Concurrent.newForeignPtr nullPtr $
    putStrLn "a was deleted"
  putStrLn "end"

Создает следующий вывод:

start
end

Я бы ожидал увидеть "a was deleted" где-то после start ..

Я не знаю, что происходит. У меня есть несколько догадок:

  • Сборщик мусора не собирает оставшиеся объекты после завершения программы
  • putStrLn перестает работать после завершения main. (кстати, я попробовал то же самое с иностранным импортированным puts и получил те же результаты)
  • Мое понимание ForeignPtr отсутствует
  • ошибка GHC? (env: GHC 6.10.3, Intel Mac)

При использовании Foreign.ForeignPtr.newForeignPtr вместо Foreign.Concurrent.newForeignPtr, похоже, работает:

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign.C.String (CString, newCString)
import Foreign.ForeignPtr (newForeignPtr)
import Foreign.Ptr (FunPtr)

foreign import ccall "&puts" puts :: FunPtr (CString -> IO ())

main :: IO ()
main = do
  putStrLn "start"
  message <- newCString "a was \"deleted\""
  a <- newForeignPtr puts message
  putStrLn "end"

выходы:

start
end
a was "deleted"

1 Ответ

6 голосов
/ 21 июня 2009

Из документации Foreign.Foreign.newForeignPtr :

Обратите внимание, что нет гарантии того, как скоро будет выполнен финализатор после того, как была удалена последняя ссылка; это зависит от деталей менеджера хранилищ Haskell. Действительно, нет никакой гарантии, что финализатор будет выполнен вообще; программа может завершиться с выдающимися финализаторами.

Таким образом, вы сталкиваетесь с неопределенным поведением: то есть, может произойти все, что угодно, и оно может меняться от платформы к платформе (как мы видели в Windows) или выпуск от выпуска.

Причина различий в поведении, которое вы видите между двумя функциями, может быть указана в документации для Foreign.Concurrent.newForeignPtr :

Эти финализаторы обязательно запускаются в отдельном потоке ...

Если в финализаторах для версии Foreign.Foreign функции используется основной поток, а в Foreign.Concurrent используется отдельный поток, вполне возможно, что основной поток завершает свою работу, не ожидая завершения работы других потоков. поэтому другие потоки никогда не смогут запустить финализацию.

Конечно, документы для версии Foreign.Concurrent действительно требуют,

Единственная гарантия - запуск финализатора до завершения программы.

Я не уверен, что они действительно должны требовать этого, поскольку, если финализаторы работают в других потоках, они могут занять произвольное количество времени, чтобы выполнить свою работу (даже заблокировать навсегда), и, следовательно, основной поток никогда не сможет заставить программу выйти. Это может противоречить этому из Control.Concurrent :

В автономной программе GHC для завершения процесса требуется только основной поток. Таким образом, все остальные разветвленные потоки просто завершатся в то же время, что и основной поток (терминология для этого вида поведения - «демонические потоки»).

...