Suppose that I have a foreign function:
-- | Turns char* of the given size into a char* of size 16.
doSomethingFfi :: Ptr CUChar -> Ptr CUChar -> CSize -> IO ()
doSomethingFfi = undefined
The function is pure, so I would like to represent it as a pure function in Haskell:
doSomething :: ByteArray bytes => bytes -> bytes
doSomething bs = unsafePerformIO $
alloc 16 $ \outPtr ->
withByteArray bs $ \inPtr ->
doSomethingFfi outPtr inPtr (fromIntegral $ length bs)
(Here I am using alloc
from memory
.)
My understanding is that the only difference between unsafePerformIO
and unsafeDupablePerformIO
is that the IO action in the latter can be silently terminated without any cleanup.
In my case above there are, essentially, two IO actions happening: 1. memory allocation; 2. foreign call. I am not concerned about 2, since it is pure, however I am worried about the memory.
Is there any guarantee that the memory allocated this way will not leak if the computation is interrupted silently? If the foreign function also required temporary storage that I had to allocate / clean up and I used alloca
for this purpose, would it still be safe to use unsafeDupablePerformIO
?
Mostly as I explained in the comments, but not quite:
alloca
As
alloca
is currently implemented, this is safe.alloca
is implemented by a call toallocaBytesAligned
, which is defined thus:This allocates pinned memory in the garbage-collected heap. If your action is aborted early, then the garbage collector will reclaim the memory it allocated sooner or later.
alloc
This is not necessarily safe, but may actually be safe in practice.
alloc
is defined using a class method,allocRet
, which different types can implement differently.Contrary to my guesses in the comments, the instances defined in
memory
all seem fine—they too allocate pinned memory. But the class does not document this as a requirement, and in principle someone could allocate memory usingForeign.Marshall.Alloc.malloc
, in which case the garbage collector will not take care of the memory automatically. Such a hypothetical implementation would have no way to ensure memory is freed if the computation aborts early.