About pointer taken by C lib in FFI.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

About pointer taken by C lib in FFI.

Magicloud Magiclouds
Hi,

Say I have some data, and `poke` into a Ptr. Then I pass the Ptr to
some function in C lib. The C lib stores the Ptr somewhere to use
later.

My question is, how does GHC handle the finalizing of the Ptr? Does it
track the "used by foreign lib"?

--
竹密岂妨流水过
山高哪阻野云飞

And for G+, please use magiclouds#gmail.com.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|

Re: About pointer taken by C lib in FFI.

Sylvain Henry-2
Hi,

Ptr has no finalizer: you have to explicitly free the attached memory if
necessary.

You can use ForeignPtr to associate finalizers to a pointer: finalizers
are functions that are called when the ForeignPtr object is to be
collected by the GC. GHC can't track if the pointer is still stored/used
by the C lib though.

Hope that helps,
Sylvain


On 12/01/2019 07:16, Magicloud Magiclouds wrote:
> Hi,
>
> Say I have some data, and `poke` into a Ptr. Then I pass the Ptr to
> some function in C lib. The C lib stores the Ptr somewhere to use
> later.
>
> My question is, how does GHC handle the finalizing of the Ptr? Does it
> track the "used by foreign lib"?
>
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|

Re: About pointer taken by C lib in FFI.

Niklas Hambüchen
In reply to this post by Magicloud Magiclouds
Hi, nice question.

> Does it track the "used by foreign lib"?

No, `Ptr` is a simple primitive numeric value, like `void *` in C itself.
GHC does not track what you do with it at all.

The lifetime and ownership of the pointer depends on how you created it.

For example, the `withCString` function of type
    withCString :: String -> (Ptr CChar -> IO a) -> IO a
    https://hackage.haskell.org/package/base-4.12.0.0/docs/Foreign-C-String.html#v:withCString
used e.g. like
    withCString "hello" $ \ptr -> do
       -- do something with with ptr here
keeps the pointer alive exactly within the (do ...) block.
Afterwards, the memory the `ptr` points to will be freed.

Similar for `allocaBytes :: Int -> (Ptr a -> IO b) -> IO b`.
You might do

    allocaBytes 1000 $ \(ptr :: Ptr void) -> do
       poke (castPtr ptr) ('c' :: Char)
       poke (castPtr ptr) (1234 :: Word64)
       -- call FFI function doing something with `ptr`

and after allocaBytes itself returns, the memory is gone.

Other functions, such as
    malloc :: Storable a => IO (Ptr a)
    mallocBytes :: Int -> IO (Ptr a)
only allocate the memory and never free it, and you need to free it later yourself (you can also use C's `free()` on the C side for that).

This may be what you want if you want the C code to take ownership of it.

In that case, you must take care that this is async-exception safe, e.g. that you don't leak the allocated memory when an async exception comes in (e.g. from the `timeout` function or the user pressing Ctrl+C and you handling it and continuing).
In general, one deals with async exceptions by using code blocks that temporarily disable them, like the `bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c` function does;
see its docs as https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Exception-Base.html#v:bracket.
Two examples of how non-bracketet `malloc` can go wrong:

Example A (no ownership change involved):

    ptr <- mallocBytes 1000
    -- async exception comes in here
    someOtherCodeDoingSomethingWith ptr
    free ptr

Example B:

    ptr <- mallocBytes 1000
    -- async exception comes in here
    ffiCodeThatChangesOwnershipToCLibrary ptr

This would be bad, your allocced-bytes are unreachable and will memory leak forever.
`bracket` can trivially solve the problem in example A, because the lifetime of `ptr` is lexically scoped.

But for the handover in example B, the lifetime is not lexically scoped.

You generally have 2 approaches to do a safe hand-over to C for non-lexically-scoped cases:

1. malloc the memory on the C side in the first place, and pass the pointer to Haskell so it can poke values in. In this case, the C side had the ownership the entire time, so it allocated and freed the memory.

2. Store the information whether Haskell still has the memory ownership somewhere, and always modify the pointer and this information together in some atomic fashion (for example using `bracket` so that it cannot be interrupted in the middle).
The pointer and a mutable Bool reference would be such an information pair.
Equivalent would be a double-pointer, where the outer pointer points to NULL to indicate that the memory is already owned by C.

Below is a sketch of how to do it with the double-pointer approach:

    bracket
      acquireResource
      releaseResource
      (\ptrPtr -> do
        ptr <- peek ptrPtr
        poke ptr ('c' :: Char)
        poke ptr ('c' :: Word64)
        mask_ $ do -- we don't want to get interrupted in this block
          ffiCodeThatChangesOwnershipToCLibrary ptr
          poke ptrPtr nullPtr
        -- do some more work here
        return yourresult
      )

      where
        acquireResource :: IO (Ptr (Ptr void))
        acquireResource = do
          ptrPtr :: Ptr (Ptr void) <- malloc
          ptr :: Ptr void <- malloc
          poke ptrPtr ptr
          return ptrPtr

        releaseResource :: Ptr (Ptr void) -> IO yourresult
        releaseResource ptrPtr = do
          -- If ptrPtr points to NULL, then the ownership change happend.
          -- In that case we don't have to free `ptr` (and we cannot, as it is NULL).
          -- Otherwise, we still own the memory, and free it.
          ptr <- peek ptrPtr
          when (ptr == nullPtr) $ free ptr
          free ptrPtr  

(It is recommended to get familiar with `bracket` and `mask_` before understanding this.)

The above works in a single-threaded case; if concurrency comes into play and you wrote code so that parts of this might be executed by different threads, you'll naturally have to put locks (e.g. `MVar`s) around the `poke ptrPtr ...` and the place where the `== nullPtr` check is done.

I hope this helps!

Niklas


PS:
I work for a Haskell consultancy. If answers like this would help move your project forward, consider us :)
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|

Re: About pointer taken by C lib in FFI.

Magicloud Magiclouds
In reply to this post by Sylvain Henry-2
Thanks. I was meant to ask ForeignPtr as well.

On Mon, Jan 14, 2019 at 6:59 AM Sylvain Henry <[hidden email]> wrote:

>
> Hi,
>
> Ptr has no finalizer: you have to explicitly free the attached memory if
> necessary.
>
> You can use ForeignPtr to associate finalizers to a pointer: finalizers
> are functions that are called when the ForeignPtr object is to be
> collected by the GC. GHC can't track if the pointer is still stored/used
> by the C lib though.
>
> Hope that helps,
> Sylvain
>
>
> On 12/01/2019 07:16, Magicloud Magiclouds wrote:
> > Hi,
> >
> > Say I have some data, and `poke` into a Ptr. Then I pass the Ptr to
> > some function in C lib. The C lib stores the Ptr somewhere to use
> > later.
> >
> > My question is, how does GHC handle the finalizing of the Ptr? Does it
> > track the "used by foreign lib"?
> >
> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.



--
竹密岂妨流水过
山高哪阻野云飞

And for G+, please use magiclouds#gmail.com.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|

Re: About pointer taken by C lib in FFI.

Magicloud Magiclouds
In reply to this post by Niklas Hambüchen
Thanks.

On Mon, Jan 14, 2019 at 10:42 PM Niklas Hambüchen <[hidden email]> wrote:

>
> Hi, nice question.
>
> > Does it track the "used by foreign lib"?
>
> No, `Ptr` is a simple primitive numeric value, like `void *` in C itself.
> GHC does not track what you do with it at all.
>
> The lifetime and ownership of the pointer depends on how you created it.
>
> For example, the `withCString` function of type
>     withCString :: String -> (Ptr CChar -> IO a) -> IO a
>     https://hackage.haskell.org/package/base-4.12.0.0/docs/Foreign-C-String.html#v:withCString
> used e.g. like
>     withCString "hello" $ \ptr -> do
>        -- do something with with ptr here
> keeps the pointer alive exactly within the (do ...) block.
> Afterwards, the memory the `ptr` points to will be freed.
>
> Similar for `allocaBytes :: Int -> (Ptr a -> IO b) -> IO b`.
> You might do
>
>     allocaBytes 1000 $ \(ptr :: Ptr void) -> do
>        poke (castPtr ptr) ('c' :: Char)
>        poke (castPtr ptr) (1234 :: Word64)
>        -- call FFI function doing something with `ptr`
>
> and after allocaBytes itself returns, the memory is gone.
>
> Other functions, such as
>     malloc :: Storable a => IO (Ptr a)
>     mallocBytes :: Int -> IO (Ptr a)
> only allocate the memory and never free it, and you need to free it later yourself (you can also use C's `free()` on the C side for that).
>
> This may be what you want if you want the C code to take ownership of it.
>
> In that case, you must take care that this is async-exception safe, e.g. that you don't leak the allocated memory when an async exception comes in (e.g. from the `timeout` function or the user pressing Ctrl+C and you handling it and continuing).
> In general, one deals with async exceptions by using code blocks that temporarily disable them, like the `bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c` function does;
> see its docs as https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Exception-Base.html#v:bracket.
> Two examples of how non-bracketet `malloc` can go wrong:
>
> Example A (no ownership change involved):
>
>     ptr <- mallocBytes 1000
>     -- async exception comes in here
>     someOtherCodeDoingSomethingWith ptr
>     free ptr
>
> Example B:
>
>     ptr <- mallocBytes 1000
>     -- async exception comes in here
>     ffiCodeThatChangesOwnershipToCLibrary ptr
>
> This would be bad, your allocced-bytes are unreachable and will memory leak forever.
> `bracket` can trivially solve the problem in example A, because the lifetime of `ptr` is lexically scoped.
>
> But for the handover in example B, the lifetime is not lexically scoped.
>
> You generally have 2 approaches to do a safe hand-over to C for non-lexically-scoped cases:
>
> 1. malloc the memory on the C side in the first place, and pass the pointer to Haskell so it can poke values in. In this case, the C side had the ownership the entire time, so it allocated and freed the memory.
>
> 2. Store the information whether Haskell still has the memory ownership somewhere, and always modify the pointer and this information together in some atomic fashion (for example using `bracket` so that it cannot be interrupted in the middle).
> The pointer and a mutable Bool reference would be such an information pair.
> Equivalent would be a double-pointer, where the outer pointer points to NULL to indicate that the memory is already owned by C.
>
> Below is a sketch of how to do it with the double-pointer approach:
>
>     bracket
>       acquireResource
>       releaseResource
>       (\ptrPtr -> do
>         ptr <- peek ptrPtr
>         poke ptr ('c' :: Char)
>         poke ptr ('c' :: Word64)
>         mask_ $ do -- we don't want to get interrupted in this block
>           ffiCodeThatChangesOwnershipToCLibrary ptr
>           poke ptrPtr nullPtr
>         -- do some more work here
>         return yourresult
>       )
>
>       where
>         acquireResource :: IO (Ptr (Ptr void))
>         acquireResource = do
>           ptrPtr :: Ptr (Ptr void) <- malloc
>           ptr :: Ptr void <- malloc
>           poke ptrPtr ptr
>           return ptrPtr
>
>         releaseResource :: Ptr (Ptr void) -> IO yourresult
>         releaseResource ptrPtr = do
>           -- If ptrPtr points to NULL, then the ownership change happend.
>           -- In that case we don't have to free `ptr` (and we cannot, as it is NULL).
>           -- Otherwise, we still own the memory, and free it.
>           ptr <- peek ptrPtr
>           when (ptr == nullPtr) $ free ptr
>           free ptrPtr
>
> (It is recommended to get familiar with `bracket` and `mask_` before understanding this.)
>
> The above works in a single-threaded case; if concurrency comes into play and you wrote code so that parts of this might be executed by different threads, you'll naturally have to put locks (e.g. `MVar`s) around the `poke ptrPtr ...` and the place where the `== nullPtr` check is done.
>
> I hope this helps!
>
> Niklas
>
>
> PS:
> I work for a Haskell consultancy. If answers like this would help move your project forward, consider us :)



--
竹密岂妨流水过
山高哪阻野云飞

And for G+, please use magiclouds#gmail.com.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.