Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make data that is allocated manually be garbage-collected in Haskell?

I'm thinking about a FFI calling some C functions from Haskell.

If a memory buffer is used to hold some data and is allocated "manually" and then it is used in Haskell computations, can I somehow rely on the garbage collector to free it when it is not needed anymore.

As for the manual allocations, there are basically two ways (but the difference doesn't seem to be essential for my question):

  • allocating a buffer in Haskell, then passing it to C function, like in fdRead
  • allocating a buffer in C (with malloc, like in GNU's asprintf), then returning the pointer to Haskell

In both examples (fdRead or asprintf) there is also a problem that the data type stored in the buffer is not suitable for a Haskell program, therefore it is copied&converted to be used in Haskell (with peekCString). (I'll put the code below.) After the copying&conversion happens, the buffer is freed (in both cases).

However, I'm thinking about a more efficient interface, where the Haskell would directly use the data as it is stored by a C function (without a conversion). (I haven't yet explored, say, alternative implementations of String and related functions: whether there is one among them which can work directly with some kind of C strings.)

If I follow this route, then there is one global problem: how to control the disposal of the allocated buffers. (For side-effects-free functions--except for the allocation--I could even wrap the calls in unsafePerformIO or declare them so that they are not an IO.)

Examples with conversion and immediate freeing

allocating in Haskell:

fdRead (here allocaBytes must care for the freeing):

-- -----------------------------------------------------------------------------
-- fd{Read,Write}

-- | Read data from an 'Fd' and convert it to a 'String' using the locale encoding.
-- Throws an exception if this is an invalid descriptor, or EOF has been
-- reached.
fdRead :: Fd
       -> ByteCount -- ^How many bytes to read
       -> IO (String, ByteCount) -- ^The bytes read, how many bytes were read.
fdRead _fd 0 = return ("", 0)
fdRead fd nbytes = do
    allocaBytes (fromIntegral nbytes) $ \ buf -> do
    rc <- fdReadBuf fd buf nbytes
    case rc of
      0 -> ioError (ioeSetErrorString (mkIOError EOF "fdRead" Nothing Nothing) "EOF")
      n -> do
       s <- peekCStringLen (castPtr buf, fromIntegral n)
       return (s, n)

-- | Read data from an 'Fd' into memory.  This is exactly equivalent
-- to the POSIX @read@ function.
fdReadBuf :: Fd
          -> Ptr Word8 -- ^ Memory in which to put the data
          -> ByteCount -- ^ Maximum number of bytes to read
          -> IO ByteCount -- ^ Number of bytes read (zero for EOF)
fdReadBuf _fd _buf 0 = return 0
fdReadBuf fd buf nbytes =
  fmap fromIntegral $
    throwErrnoIfMinus1Retry "fdReadBuf" $
      c_safe_read (fromIntegral fd) (castPtr buf) nbytes

foreign import ccall safe "read"
   c_safe_read :: CInt -> Ptr CChar -> CSize -> IO CSsize

allocating in C

getValue.c:

#define _GNU_SOURCE
#include <stdio.h>

#include "getValue.h"

char * getValue(int key) {
  char * value;
  asprintf(&value, "%d", key); // TODO: No error handling!
  // If memory allocation wasn't possible, or some other error occurs,  these  functions  will
  // return -1, and the contents of strp is undefined.
  return value;
}

GetValue.hs (here I explicitly call free after the conversion is actually done):

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign hiding (unsafePerformIO)
import Foreign.Ptr
import Foreign.C.Types

import Foreign.C.String(peekCString)

import System.IO.Unsafe

getValue :: Int -> IO String
getValue key = do
  valptr <- c_safe_getValue (fromIntegral key)
  value <- peekCString valptr
  c_safe_free valptr
  return value

foreign import ccall safe "getValue.h getValue" c_safe_getValue :: CInt -> IO (Ptr CChar)
foreign import ccall safe "stdlib.h free" c_safe_free :: Ptr a -> IO ()

value :: Int -> String
value = unsafePerformIO . getValue -- getValue has no side-effects, so we wrap it.

{- A simple test: -}
main1 = putStrLn (value 2)

{- A test with an infinite list, which employs laziness: -}
keys = [-5..]
results = map value keys

main = foldr (>>) 
             (return ())
             (map putStrLn (take 20 results))

If there wasn't the (ineffective) conversion&copying step, I would need to rely on garbage collector for freeing, but have no idea how to define such things in Haskell.

like image 973
imz -- Ivan Zakharyaschev Avatar asked Feb 17 '15 14:02

imz -- Ivan Zakharyaschev


People also ask

Does Haskell have garbage collection?

The Haskell runtime system employs a generational garbage collector (GC) with two generations 2. Generations are numbered starting with the youngest generation at zero.

Why does Haskell need garbage collection?

The answer is that a GC is required under the hood to reclaim the heap objects that the language MUST create. For example. A pure function needs to create heap objects because in some cases it has to return them. That means that they can't be allocated on the stack.

What is garbage collection data?

Garbage collection is a term used in computer programming to describe the process of finding and deleting objects which are no longer being referenced by other objects. In other words, garbage collection is the process of removing any objects which are not being used by any other objects.

Is scheme garbage collected?

The Scheme heap is garbage collected, meaning that the Scheme system automatically cleans up after you. Every now and then, the system figures out which objects aren't in use anymore, and reclaims their storage.


1 Answers

The ForeignPtr type acts as a Ptr with an attached finalizer. When the ForeignPtr gets garbage collected, the finalizer is run, and can call the C side to free the pointer using the proper function.

Since the pointer is no longer accessible from Haskell, this is typically the right moment to free it.

like image 53
chi Avatar answered Oct 04 '22 17:10

chi