2014-05-21 11 views
7

Ho giocato con il pacchetto UNIX conduit-extra, che fondamentalmente consente una facile creazione di un server utilizzando socket di dominio UNIX, in particolare utilizzando lo runUnixServer funciton.Qual è il modo corretto di ripulire le risorse usando ResourceT?

Il problema è che dopo la presenza della funzione non pulisce il file socket, il che significa che deve essere pulito manualmente. Ecco un semplice esempio, che in pratica crea un server echo.

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

ho google un po 'intorno e ha scoperto che il modo corretto di gestire le risorse qui è quello di utilizzare il pacchetto resourcet. Anche se il problema è che la maggior parte delle API in risorse mi aspettano di allocare la risorsa da sola, che non è il caso di runUnixSever, che non restituisce nulla.

In un primo momento ho pensato che avrei potuto usare register, per registrare una funzione che elimina il file, come ad esempio il seguente

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

C'è un problema con questo approccio, però, almeno per quanto riguarda la documentazione per allocate dice:

Questo è quasi identico a chiamare l'allocazione e quindi registrare l'azione di rilascio, ma questo gestisce correttamente il mascheramento delle eccezioni asincrone.

Ciò significa che register di per sé non gestisce eccezioni asincrone? Se sì, potrebbe essere un problema quando uno dei gestori generato dallo runUnixServer (i documenti dicono che genera un thread per ogni client) genera un errore?

Una terza e ultima soluzione che ho trovato è l'utilizzo di allocate, per assicurarsi che le eccezioni asincrone siano gestite correttamente (non sono sicuro che sia davvero necessario in questo caso).

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

Ma questa è davvero la soluzione migliore? Dal momento che sto creando un valore che non userò mai (return 1) e quindi utilizzando una funzione const per ignorare quel valore nel finalizzatore.

risposta

9

Prima di affrontare la questione resourcet:

  1. resourcet non è necessaria in questo caso. Puoi semplicemente utilizzare la funzione finally per qualcosa di simile, ad es. runUnixServer settings (\ad -> ...) infine removeLink "foobar.sock".
  2. Questo in realtà sembra un comportamento problematico. Il modello generalmente accettato nel conduit è che, se si assegna una risorsa, si è responsabili della sua pulizia. Non ho scritto il codice socket unix, quindi potrebbe esserci un motivo per l'autore di averlo fatto diversamente qui. Ma vale la pena aprire un bug report.

Detto questo, il codice iniziale con register va bene. L'unico problema che vedo è se viene generata un'eccezione prima che venga creato foobar.sock, sebbene la mia soluzione finally sia vulnerabile anche a questo.

Il commento destinare circa vs registro ha a che fare con il codice che è simile al seguente:

handle <- openFile fp ReadMode 
register $ hClose handle 

Questo codice è vulnerabile a un'eccezione asincrona essere gettato tra i openFile e register chiamate. Dato che non stai allocando una risorsa come questa, register va bene.

+0

Grazie per una risposta super veloce! –