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.
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 -> ...)
finallyremoveLink "foobar.sock"
.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With