Haskell FFI:ForeignPtr 似乎没有被释放(也许是 GHC bug?)

发布于 2024-07-24 21:13:34 字数 1209 浏览 2 评论 0原文

考虑以下代码片段

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

我本来希望在 start 之后的某个地方看到“a was returned”..

我不知道发生了什么。 我有一些猜测:

  • 当程序完成时,垃圾收集器不会收集剩余的对象
  • putStrLnmain 完成后停止工作。 (顺便说一句,我对国外导入的 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"

Consider the following code snippet

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"

It produces the following output:

start
end

I would had expected to see "a was deleted" somewhere after start..

I don't know what's going on. I have a few guesses:

  • The garbage collector doesn't collect remaining objects when the program finishes
  • putStrLn stops working after main finishes. (btw I tried same thing with foreignly imported puts and got the same results)
  • My understanding of ForeignPtr is lacking
  • GHC bug? (env: GHC 6.10.3, Intel Mac)

When using Foreign.ForeignPtr.newForeignPtr instead of Foreign.Concurrent.newForeignPtr it seems to work:

{-# 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"

outputs:

start
end
a was "deleted"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

∞觅青森が 2024-07-31 21:13:34

来自 Foreign.Foreign 的文档。新的ForeignPtr

请注意,无法保证在删除最后一个引用后多久执行终结器; 这取决于 Haskell 存储管理器的细节。 事实上,根本无法保证终结器会被执行; 程序可能会因终结器未完成而退出。

因此,您会遇到未定义的行为:即,任何事情都可能发生,并且它可能会因平台而异(正如我们在 Windows 下看到的那样)或版本不同。

Foreign.Concurrent.newForeignPtr

这些终结器必须在单独的线程中运行...

如果该函数的Foreign.Foreign版本的终结器使用主线程,但Foreign.Concurrent版本的终结器使用单独的线程,那么主线程很可能会在没有关闭的情况下关闭等待其他线程完成其工作,因此其他线程永远无法运行终结。

当然,Foreign.Concurrent 版本的文档确实声称,

唯一的保证是终结器在程序终止之前运行。

我不确定他们实际上应该声明这一点,因为如果终结器在其他线程中运行,他们可以花费任意数量的时间来完成他们的工作(甚至永远阻塞),因此主线程永远不会能够强制程序退出。 这将与 Control.Concurrent

在独立的 GHC 程序中,只需要终止主线程即可终止进程。 因此,所有其他分叉线程将与主线程同时终止(这种行为的术语是“守护线程”)。

From the documentation of Foreign.Foreign.newForeignPtr:

Note that there is no guarantee on how soon the finaliser is executed after the last reference was dropped; this depends on the details of the Haskell storage manager. Indeed, there is no guarantee that the finalizer is executed at all; a program may exit with finalizers outstanding.

So you're running into undefined behaviour: i.e., anything can happen, and it may change from platform to platform (as we saw under Windows) or release to release.

The cause of the difference in behaviour you're seeing between the two functions may be hinted at by the documentation for Foreign.Concurrent.newForeignPtr:

These finalizers necessarily run in a separate thread...

If the finalizers for the Foreign.Foreign version of the function use the main thread, but the Foreign.Concurrent ones use a separate thread, it could well be that the main thread shuts down without waiting for other threads to complete their work, so the other threads never get to run the finalization.

Of course, the docs for the Foreign.Concurrent version do claim,

The only guarantee is that the finalizer runs before the program terminates.

I'm not sure that they actually ought to be claiming this, since if the finalizers are running in other threads, they can take an arbitrary amount of time to do their work (even block forever), and thus the main thread would never be able to force the program to exit. That would conflict with this from Control.Concurrent:

In a standalone GHC program, only the main thread is required to terminate in order for the process to terminate. Thus all other forked threads will simply terminate at the same time as the main thread (the terminology for this kind of behaviour is "daemonic threads").

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文