2012-05-09 7 views
9

Estoy escribiendo un daemon que lee algo de un archivo pequeño, lo modifica y lo vuelve a escribir en el mismo archivo. Necesito asegurarme de que cada archivo se cierra rápidamente después de leer antes de intentar escribir en él. También necesito asegurarme de que cada archivo se cierra inmediatamente después de escribir, porque de vez en cuando puedo leer de nuevo.Asegurar que los archivos se cierran rápidamente

He estudiado el uso de binary-strict en lugar de binary, pero parece que solo proporciona un Get estricto, no un Put estricto. Mismo problema con System.IO.Strict. Y al leer la documentación estrictamente binaria, no estoy seguro de que realmente resuelva mi problema de garantizar que los archivos se cierren rápidamente. ¿Cuál es la mejor manera de manejar esto? DeepSeq?

Aquí hay un ejemplo muy simplificado que le dará una idea de la estructura de mi aplicación. Este ejemplo termina con

*** Exception: test.dat: openBinaryFile: resource busy (file is locked)

por razones obvias.

import Data.Binary (Binary, encode, decode) 
import Data.ByteString.Lazy as B (readFile, writeFile) 
import Codec.Compression.GZip (compress, decompress) 

encodeAndCompressFile :: Binary a => FilePath -> a -> IO() 
encodeAndCompressFile f = B.writeFile f . compress . encode 

decodeAndDecompressFile :: Binary a => FilePath -> IO a 
decodeAndDecompressFile f = return . decode . decompress =<< B.readFile f 

main = do 
    let i = 0 :: Int 
    encodeAndCompressFile "test.dat" i 
    doStuff 

doStuff = do 
    i <- decodeAndDecompressFile "test.dat" :: IO Int 
    print i 
    encodeAndCompressFile "test.dat" (i+1) 
    doStuff 

Respuesta

7

Considere utilizar un paquete como conduit, pipes, iteratee o enumerator. Proporcionan muchos de los beneficios del IO perezoso (código más simple, huella de memoria potencialmente más pequeña) sin el IO lento. Aquí está un ejemplo utilizando conduit y cereal:

import Data.Conduit 
import Data.Conduit.Binary (sinkFile, sourceFile) 
import Data.Conduit.Cereal (sinkGet, sourcePut) 
import Data.Conduit.Zlib (gzip, ungzip) 
import Data.Serialize (Serialize, get, put) 

encodeAndCompressFile :: Serialize a => FilePath -> a -> IO() 
encodeAndCompressFile f v = 
    runResourceT $ sourcePut (put v) $$ gzip =$ sinkFile f 

decodeAndDecompressFile :: Serialize a => FilePath -> IO a 
decodeAndDecompressFile f = do 
    val <- runResourceT $ sourceFile f $$ ungzip =$ sinkGet get 
    case val of 
    Right v -> return v 
    Left err -> fail err 

main = do 
    let i = 0 :: Int 
    encodeAndCompressFile "test.dat" i 
    doStuff 

doStuff = do 
    i <- decodeAndDecompressFile "test.dat" :: IO Int 
    print i 
    encodeAndCompressFile "test.dat" (i+1) 
    doStuff 
+0

Conector desvergonzado: 'pipes' está a punto de salir con una administración de recursos rápida, determinista y composable dentro de una semana. –

+0

@GabrielGonzalez Excelente. Hazme ping cuando se haya lanzado y actualizaré esta respuesta. –

+1

Ya está hecho.Acabo de anunciarlo en [reddit] (http://www.reddit.com/r/haskell/comments/txkb0/pipes_20_pipe_finalization/). –

11

Todos los 'puts' o 'write' a los archivos son estrictos. El acto de writeFile exige que se evalúen todos los datos de Haskell para colocarlos en el disco.

Entonces, lo que necesita para concentrarse es la lectura perezosa de la entrada . En su ejemplo anterior, ambos leen el archivo con pereza y luego decodifican perezosamente.

En su lugar, intente leer el archivo estrictamente (por ejemplo, con cadenas de bytes estrictas), y todo irá bien.

+0

Estoy confundido. Imagino que 'i' está inicialmente ligado a un thunk en' doStuff', y que no se ha producido IO desde que usamos el 'readFile' perezoso. Sin embargo, una vez que 'imprimimos i', ¿eso no fuerza la evaluación de 'i', y la finalización de todo el IO? ¿'Descomprimir' no leyó todo el archivo, por lo que se dejó abierto? – pat

+0

Don, su explicación me ayudó a entender por qué no necesito versiones estrictas de 'poner' o 'escribir'; gracias por eso. Sin embargo, creo que también necesitaría encontrar una versión estricta de descompresión. Finalmente fui con la solución de Nathan porque me resulta un poco más fácil seguir el código. – mhwombat

2

Una alternativa al uso conductos et al. sería simplemente usar System.IO, lo que le permitirá controlar explícitamente cuándo se cierran los archivos con respecto al orden de ejecución de IO.

Usted puede utilizar openBinaryFile seguido por las operaciones normales de lectura (probablemente los de Data.ByteString) y hClose cuando haya terminado con él, o withBinaryFile, que cierra el archivo automáticamente (pero ten cuidado this sort of problem).

Sea cual sea el método que utilice, como dijo Don, es probable que desee leer como una cadena de bytes estricta y luego convertir el estricto a perezoso después con fromChunks.

Cuestiones relacionadas