Haskell FFI:ForeignPtr似乎没有被释放(也许是一个GHC错误?)

时间:2022-03-12 17:04:04

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)
  • putStrLn在主要完成后停止工作。 (顺便说一句,我和国外进口的推杆一样尝试了相同的东西,得到了相同的结果)

  • My understanding of ForeignPtr is lacking
  • 我对ForeignPtr的理解很缺乏

  • GHC bug? (env: GHC 6.10.3, Intel Mac)
  • GHC错误? (环境:GHC 6.10.3,Intel Mac)

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

当使用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"

outputs:

start
end
a was "deleted"

1 个解决方案

#1


From the documentation of Foreign.Foreign.newForeignPtr:

来自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.

请注意,无法保证在最后一次引用被删除后执行终结器的时间。这取决于Haskell存储管理器的细节。实际上,并不能保证终结器完全被执行;程序可以退出并使用终结器。

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.

所以你遇到了未定义的行为:即,任何事情都可能发生,并且它可能会从平台变为平台(正如我们在Windows下看到的那样)或发布到发布版。

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:

您可以通过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.

如果函数的Foreign.Foreign版本的终结器使用主线程,但是Foreign.Concurrent使用单独的线程,则很可能主线程关闭而不等待其他线程完成其工作,因此其他线程永远不会运行完成。

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

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

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:

我不确定他们究竟应该声称这个,因为如果终结器在其他线程中运行,他们可以花费任意的时间来完成他们的工作(甚至永远阻止),因此主线程将永远不会能够强制程序退出。这将与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").

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

#1


From the documentation of Foreign.Foreign.newForeignPtr:

来自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.

请注意,无法保证在最后一次引用被删除后执行终结器的时间。这取决于Haskell存储管理器的细节。实际上,并不能保证终结器完全被执行;程序可以退出并使用终结器。

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.

所以你遇到了未定义的行为:即,任何事情都可能发生,并且它可能会从平台变为平台(正如我们在Windows下看到的那样)或发布到发布版。

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:

您可以通过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.

如果函数的Foreign.Foreign版本的终结器使用主线程,但是Foreign.Concurrent使用单独的线程,则很可能主线程关闭而不等待其他线程完成其工作,因此其他线程永远不会运行完成。

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

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

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:

我不确定他们究竟应该声称这个,因为如果终结器在其他线程中运行,他们可以花费任意的时间来完成他们的工作(甚至永远阻止),因此主线程将永远不会能够强制程序退出。这将与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").

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