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

conduit, haskell, resources, sockets

Solution

Before addressing the `resourcet` question:

- `resourcet` is not needed in this case. You can just use the `finally` function for something like this, e.g. `runUnixServer settings (\ad -> ...)`finally`removeLink "foobar.sock"`.

- 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.

Problem

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.

Original source