|
| 1 | +-- | Implementations using the real file system. |
| 2 | +-- |
| 3 | +-- The implementation of the 'HasBlockIO' interface provided in this module is |
| 4 | +-- platform-dependent. Most importantly, on Linux, the implementation of |
| 5 | +-- 'submitIO' is backed by @blockio-uring@: a library for asynchronous I/O. On |
| 6 | +-- Windows and MacOS, the implementation of 'submitIO' only supports serial I/O. |
1 | 7 | module System.FS.BlockIO.IO ( |
| 8 | + -- * Implementation details #impl# |
| 9 | + -- $impl |
| 10 | + |
| 11 | + -- * Initialisation |
2 | 12 | ioHasBlockIO |
3 | 13 | , withIOHasBlockIO |
| 14 | + -- ** Parameters |
4 | 15 | , IOI.IOCtxParams (..) |
5 | 16 | , IOI.defaultIOCtxParams |
| 17 | + -- ** Unsafe |
| 18 | + , unsafeFromHasFS |
| 19 | + , withUnsafeFromHasFS |
6 | 20 | ) where |
7 | 21 |
|
8 | 22 | import Control.Exception (bracket) |
9 | | -import System.FS.API (HasFS) |
| 23 | +import System.FS.API (HasFS, MountPoint) |
10 | 24 | import System.FS.BlockIO.API (HasBlockIO (..)) |
11 | 25 | import qualified System.FS.BlockIO.Internal as I |
12 | 26 | import qualified System.FS.BlockIO.IO.Internal as IOI |
13 | | -import System.FS.IO (HandleIO) |
| 27 | +import System.FS.IO (HandleIO, ioHasFS) |
14 | 28 |
|
15 | | --- | Platform-dependent IO instantiation of 'HasBlockIO'. |
16 | | -ioHasBlockIO :: |
| 29 | +{- $impl |
| 30 | +
|
| 31 | + Though the 'HasBlockIO' interface tries to capture uniform behaviour, each |
| 32 | + function in this implementation for the real file system can have subtly |
| 33 | + different effects depending on the underlying patform. For example, some |
| 34 | + features are not provided by some operating systems, and in some cases the |
| 35 | + features behave subtly differently for different operating systems. For this |
| 36 | + reason, we include below some documentation about the effects of calling the |
| 37 | + interface functions on different platforms. |
| 38 | +
|
| 39 | + Note: if the @serialblockio@ Cabal flag is enabled, then the Linux implementation |
| 40 | + uses a mocked context and serial I/O for 'close' and 'submitIO', just like the |
| 41 | + MacOS and Windows implementations do. |
| 42 | +
|
| 43 | + [IO context]: When an instance of the 'HasBlockIO' interface for Linux |
| 44 | + systems is initialised, an @io_uring@ context is created using the |
| 45 | + @blockio-uring@ package and stored in the 'HasBlockIO' closure. For uniform |
| 46 | + behaviour, each other platform creates and stores a mocked IO context that |
| 47 | + has the same open/closed behaviour as an @io_uring@ context. In summary, |
| 48 | + each platform creates: |
| 49 | +
|
| 50 | + * Linux: an @io_uring@ context provided by the @blockio-uring@ package |
| 51 | + * MacOS: a mocked context using an @MVar@ |
| 52 | + * Windows: a mocked conext using an @MVar@ |
| 53 | +
|
| 54 | + ['close']: |
| 55 | +
|
| 56 | + * Linux: close the @io_uring@ context through the @blockio-uring@ package |
| 57 | + * MacOS: close the mocked context |
| 58 | + * Windows: close the mocked context |
| 59 | +
|
| 60 | + ['submitIO']: Submit a batch of I/O operations using: |
| 61 | +
|
| 62 | + * Linux: the @submitIO@ function from the @blockio-uring@ package |
| 63 | + * MacOS: serial I/O using a 'HasFS' |
| 64 | + * Windows: serial I/O using a 'HasFS' |
| 65 | +
|
| 66 | + ['hSetNoCache']: |
| 67 | +
|
| 68 | + * Linux: set the @O_DIRECT@ flag |
| 69 | + * MacOS: set the @F_NOCACHE@ flag |
| 70 | + * Windows: no-op |
| 71 | +
|
| 72 | + ['hAdvise']: |
| 73 | +
|
| 74 | + * Linux: perform @posix_fadvise(2)@ |
| 75 | + * MacOS: no-op |
| 76 | + * Windows: no-op |
| 77 | +
|
| 78 | + ['hAllocate']: |
| 79 | +
|
| 80 | + * Linux: perform @posix_fallocate(2)@ |
| 81 | + * MacOS: no-op |
| 82 | + * Windows: no-op |
| 83 | +
|
| 84 | + ['tryLockFile']: This uses different locking methods depending on the OS. |
| 85 | +
|
| 86 | + * Linux: Open file descriptor (OFD) |
| 87 | + * MacOS: @flock@ |
| 88 | + * Windows: @LockFileEx@ |
| 89 | +
|
| 90 | + ['hSynchronise']: |
| 91 | +
|
| 92 | + * Linux: perform @fsync(2)@ |
| 93 | + * MacOS: perform @fsync(2)@ |
| 94 | + * Windows: perform @flushFileBuffers@ |
| 95 | +
|
| 96 | + ['synchroniseDirectory']: |
| 97 | +
|
| 98 | + * Linux: perform @fsync(2)@ |
| 99 | + * MacOS: perform @fsync(2)@ |
| 100 | + * Windows: no-op |
| 101 | +
|
| 102 | + ['createHardLink']: |
| 103 | +
|
| 104 | + * Linux: perform @link@ |
| 105 | + * MacOS: perform @link@ |
| 106 | + * Windows: perform @CreateHardLinkW@ |
| 107 | +-} |
| 108 | + |
| 109 | +-- | An implementation of the 'HasBlockIO' interface using the real file system. |
| 110 | +-- |
| 111 | +-- Make sure to use 'close' the resulting 'HasBlockIO' when it is no longer |
| 112 | +-- used. 'withUnsafeFromHasFS' does this automatically. |
| 113 | +-- |
| 114 | +-- === Unsafe |
| 115 | +-- |
| 116 | +-- You will probably want to use 'ioHasBlockIO' or 'withIOHasBlockIO' instead. |
| 117 | +-- |
| 118 | +-- Only a 'HasFS' for the real file system, like 'ioHasFS', should be passed to |
| 119 | +-- 'unsafeFromHasFS'. Technically, one could pass a 'HasFS' for a simulated file |
| 120 | +-- system, but then the resulting 'HasBlockIO' would contain a mix of simulated |
| 121 | +-- and real functions, which is probably not what you want. |
| 122 | +unsafeFromHasFS :: |
17 | 123 | HasFS IO HandleIO |
18 | 124 | -> IOI.IOCtxParams |
19 | 125 | -> IO (HasBlockIO IO HandleIO) |
20 | | -ioHasBlockIO = I.ioHasBlockIO |
| 126 | +unsafeFromHasFS = I.ioHasBlockIO |
21 | 127 |
|
22 | | -withIOHasBlockIO :: |
| 128 | +-- | Perform an action using a 'HasBlockIO' instance that is only open for the |
| 129 | +-- duration of the action. |
| 130 | +-- |
| 131 | +-- The 'HasBlockIO' is initialised using 'unsafeFromHasFS'. |
| 132 | +-- |
| 133 | +-- === Unsafe |
| 134 | +-- |
| 135 | +-- You will probably want to use 'ioHasBlockIO' or 'withIOHasBlockIO' instead. |
| 136 | +-- |
| 137 | +-- Only a 'HasFS' for the real file system, like 'ioHasFS', should be passed to |
| 138 | +-- 'withUnsafeFromHasFS'. Technically, one could pass a 'HasFS' for a simulated |
| 139 | +-- file system, but then the resulting 'HasBlockIO' would contain a mix of |
| 140 | +-- simulated and real functions, which is probably not what you want. |
| 141 | +withUnsafeFromHasFS :: |
23 | 142 | HasFS IO HandleIO |
24 | 143 | -> IOI.IOCtxParams |
25 | 144 | -> (HasBlockIO IO HandleIO -> IO a) |
26 | 145 | -> IO a |
27 | | -withIOHasBlockIO hfs params action = |
28 | | - bracket (ioHasBlockIO hfs params) (\HasBlockIO{close} -> close) action |
| 146 | +withUnsafeFromHasFS hfs params = |
| 147 | + bracket (unsafeFromHasFS hfs params) (\HasBlockIO{close} -> close) |
| 148 | + |
| 149 | +-- | An implementation of the 'HasBlockIO' interface using the real file system. |
| 150 | +-- |
| 151 | +-- Make sure to use 'close' the resulting 'HasBlockIO' when it is no longer |
| 152 | +-- used. 'withIOHasBlockIO' does this automatically. |
| 153 | +-- |
| 154 | +-- The 'HasFS' interface is instantiated using 'ioHasFS'. |
| 155 | +ioHasBlockIO :: |
| 156 | + MountPoint |
| 157 | + -> IOI.IOCtxParams |
| 158 | + -> IO (HasFS IO HandleIO, HasBlockIO IO HandleIO) |
| 159 | +ioHasBlockIO mount params = do |
| 160 | + let hfs = ioHasFS mount |
| 161 | + hbio <- unsafeFromHasFS hfs params |
| 162 | + pure (hfs, hbio) |
| 163 | + |
| 164 | +-- | Perform an action using a 'HasFS' and a 'HasBlockIO' instance. The latter |
| 165 | +-- is only open for the duration of the action. |
| 166 | +-- |
| 167 | +-- The 'HasFS' and 'HasBlockIO' interfaces are initialised using 'ioHasBlockIO'. |
| 168 | +withIOHasBlockIO :: |
| 169 | + MountPoint |
| 170 | + -> IOI.IOCtxParams |
| 171 | + -> (HasFS IO HandleIO -> HasBlockIO IO HandleIO -> IO a) |
| 172 | + -> IO a |
| 173 | +withIOHasBlockIO mount params action = |
| 174 | + bracket (ioHasBlockIO mount params) (\(_, HasBlockIO{close}) -> close) (uncurry action) |
0 commit comments