My application needs to have several resources open during runtime. I am achieving this by mapping over openFile
once at the start of the application.
let filePaths = ["/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2"]
fileHandles <- mapM (`openFile` ReadWriteMode) filePaths
This code is not safe as it might work for the first 2 file paths but then throw an exception when opening the 3rd file path. In that case i need to close the first 2 file paths that were opened already so i can exit the function without leaking resources. i have looked at the functions and patterns from Control.Exception
but not found anything that helps in this case. I have not looked at ResourceT yet. Does it help in this situation?
I think i am looking for a function signature that is similar to this:
safeMapM:: [a] -> (a -> IO b) -> (b -> IO()) -> [b]
where (b -> IO())
is the clean up function that is called when some exception occurred.
Solutions i could think of that are probably not good:
Maybe
. Exceptions can be catched and result in a Nothing
. the mapM
could always finish and i could check for Nothing/Exception afterwards and then close all successfully opened file handles by their Just Handle
.System.IO.MemoryMappedFiles Namespace. The System.IO.MemoryMappedFiles namespace provides classes for using a memory-mapped file, which maps the contents of a file to an application's logical address space. Represents a memory-mapped file. Represents the permissions that can be granted for file access and operations on memory-mapped files.
A memory-mapped file can be associated with an optional name that allows the memory-mapped file to be shared with other processes. You can create multiple views of the memory-mapped file, including views of parts of the file.
The file remains referenced as long as a mapping exists over the file. If a mapping already exists for the portion of the processes address space that is to be mapped and the value MAP_FIXED was specified for flags , then the previous mappings for the affected pages are implicitly unmapped.
Memory Mapped Files Some information relates to prerelease product that may be substantially modified before it’s released. Microsoft makes no warranties, express or implied, with respect to the information provided here. Represents a memory-mapped file.
If I understand correctly, the problem is how to ensure safe closing of all handles when an exception happens.
For a single file, the usual way of ensuring safety is withFile
. The complication here is that you want to open a sequence of files.
Perhaps we could write this auxiliary function that performs nested allocations of withFile
and passes the list of Handle
s to a callback in the innermost level:
nestedWithFile :: [FilePath] -> IOMode -> ([Handle] -> IO r) -> IO r
nestedWithFile filePaths mode callback = go [] filePaths
where
go acc [] =
callback acc -- innermost invocation, protected by the withFiles
go acc (p : ps) =
withFile p mode (\handle -> go (acc ++ [handle]) ps)
Another way of doing it begins by realizing that we are doing something with a replicateM
flavor: we are performing an "effect" n
times, and returning a list with the results. But what would be the "effect" (that is, the Applicative
) here? It seems to be "protecting the allocation of a resource with a wrapper function that ensures release".
This kind of effect seems to require some control over the "rest of the computation", because when the "rest of the computation" finishes in any way, the finalizer must still be run. This points us to the continuation monad transformer, ContT
:
import Control.Monad
import Control.Monad.Trans.Cont
import System.IO
openFile' :: FilePath -> ContT r IO Handle
openFile' filePath = ContT (withFile filePath ReadWriteMode)
openSameFileSeveralTimes :: Int -> FilePath -> ContT r IO [Handle]
openSameFileSeveralTimes count filePath = replicateM count (openFile' filePath)
-- The handles are freed when the ([Handle] -> IO r) callback exits
useHandles :: ContT r IO [Handle] -> ([Handle] -> IO r) -> IO r
useHandles = runContT
The continuation transformer might be a bit too general for this purpose. There are libraries like managed which follow the same basic mechanism but are more focused on resource handling.
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