What is the correct way of cleaning up resources using ResourceT?
Asked Answered
P

1

7

I've been playing around with conduit-extra's UNIX package, which basically allows for an easy creation of a server using UNIX domain sockets, specifically using the runUnixServer funciton.

The problem is that after the function exists it doesn't cleanup the socket file, which means it needs to be cleaned up manually. Here's a simple example, which basically creates an echo server.

main :: IO ()
main = do
  let settings = serverSettings "foobar.sock"
  runUnixServer settings (\ad -> (appSource ad) $$ (appSink ad))

I've google around a bit and found that the correct way to handle the resources here is by using the resourcet package. Though the problem is that most of the APIs in resources expect me to allocate the resource myself, which isn't the case of runUnixSever, which doesn't return anyhting.

At first I thought I could use register, to register a function that removes the file, such as the following

main :: IO ()
main = runResourceT $ do
  register $ removeLink "foobar.sock"
  let settings = serverSettings "foobar.sock"
  liftIO $ runUnixServer settings (\ad -> (appSource ad) $$ (appSink ad))

There's a problem with this approach though, at least as far as the documentation for allocate says:

This is almost identical to calling the allocation and then registering the release action, but this properly handles masking of asynchronous exceptions.

Does this mean that register in itself doesn't handle asynchronous exceptions? If so, could that be a problem when one of the handlers spawned by the runUnixServer (docs say it spawns a thread for each client) raises an error?

A third and final solution that I came up with is by using allocate, in order to make sure that the asynchronous exceptions are handled properly (I'm not sure if it is really necessary in this case).

main :: IO ()
main = runResourceT $ do
  allocate (return 1) (const $ removeLink "foobar.sock")
  let settings = serverSettings "foobar.sock"
  liftIO $ runUnixServer settings (\ad -> (appSource ad) $$ (appSink ad))

But is this really the best solution? Since I'm creating a value which I'll never use (return 1) and then using a const function to ignore that value in the finalizer.

Pat answered 21/5, 2014 at 23:46 Comment(0)
G
9

Before addressing the resourcet question:

  1. resourcet is not needed in this case. You can just use the finally function for something like this, e.g. runUnixServer settings (\ad -> ...)finallyremoveLink "foobar.sock".
  2. This actually looks like problematic behavior. The generally accepted pattern in conduit is that, if you allocate a resource, you're responsible for cleaning it up. I didn't write the unix socket code, so there might be a reason for the author to have done it differently here. But it's worth opening up a bug report.

That said, your initial code with register is fine. The only problem I see is if an exception is thrown before foobar.sock is created, though my finally solution is vulnerable to that too.

The comment about allocate vs register has to do with code that looks like the following:

handle <- openFile fp ReadMode
register $ hClose handle

This code is vulnerable to an async exception being thrown between the openFile and register calls. Since you are not allocating a resource like this, register is fine.

Gabar answered 22/5, 2014 at 4:17 Comment(1)
Thanks for a super quick response!Pat

© 2022 - 2024 — McMap. All rights reserved.