Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When using bracket with a Ptr as resource, can it be replaced with ForeignPtr?

Tags:

haskell

ffi

My code uses a resource that can be described as a pointer; I'll use a void pointer here for simplicity. The resource must be closed after the computation with it finishes, so the Control.Exception.bracket function is a natural choice to make sure the code won't leak if an error occurs:

run :: (Ptr () -> IO a) -> IO a
run action = bracket acquireResource closeResource action
-- no eta reduction for clarity

The downside of this pattern is that the resource will always be closed after action completes. AFAIU this means that it isn't possible to do something like

cont <- run $ \ptr -> do
  a <- someAction ptr
  return (\x -> otherActionUsingResource ptr a x)
cont ()

The resource will already be close by the time cont is executed. Now my approach is to use a ForeignPtr instead:

run' :: (ForeignPtr () -> IO a) -> IO a
run' action = do
  ptr <- acquireResource
  foreignPtr <- newForeignPtr closeResourceFunPtr ptr
  action foreignPtr

Now it seems that this is roughly equivalent to the first version, minor typing differences and resource closing latency aside. However, I do wonder whether this is true, or if I miss something. Can some error conditions can lead to different outcomes with those two versions? Are ForeignPtr safe to use in this way?

like image 656
tarleb Avatar asked Oct 15 '25 20:10

tarleb


1 Answers

If you want to do this, I'd recommend avoiding that run', which makes it look like you're going to close the resource. Do something like this instead.

acquire :: IO (ForeignPtr ())
acquire action = mask $ \unmask -> do
  ptr <- unmask acquireResource
  newForeignPtr closeResourceFunPtr ptr

As Carl pointed out in a comment, it's important that exceptions be masked between acquiring the resource and installing the finalizer to close it; otherwise an asynchronous exception could be delivered in between and cause a resource leak.

The challenge with anything of this sort is that you're leaving it up to the user and/or garbage collector to make sure the resource gets freed. The original Ptr-based code made the lifespan explicit. Now it's not. Many people believe that explicit lifespans are better for critical resources. What ForeignPtr gives you, automatic finalization by the GC, these people consider poor design. So think carefully! Is this a cheap resource (like a little bit of malloced memory) that you just want to free eventually? Or is it something expensive (like a file descriptor) that you really want to be sure about?


Side note: Ptr () and ForeignPtr () aren't very idiomatic. Usually the type argument should be a Haskell type representing whatever is being pointed to.

like image 100
dfeuer Avatar answered Oct 17 '25 11:10

dfeuer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!