Хаскелл ФФИ:ForeignPtr, похоже, не освобождается (может быть, ошибка GHC?)

StackOverflow https://stackoverflow.com/questions/1022336

  •  06-07-2019
  •  | 
  •  

Вопрос

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

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 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"
Это было полезно?

Решение

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

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

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

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

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

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

Конечно, в документации к версии Foreign.Concurrent утверждается:

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top