summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ghci7
-rw-r--r--.gitignore1
-rw-r--r--Makefile4
-rw-r--r--bittorrent.cabal57
-rw-r--r--src/System/IO/MMap/Fixed.hs203
-rw-r--r--tests/MMap.hs45
6 files changed, 301 insertions, 16 deletions
diff --git a/.ghci b/.ghci
index 21c0f3a6..6f391cbf 100644
--- a/.ghci
+++ b/.ghci
@@ -1,5 +1,10 @@
1import Data.Serialize as S 1import Data.Serialize as S
2
2import Network 3import Network
3import Network.Socket hiding (send, sendTo, recv, recvFrom) 4import Network.Socket hiding (send, sendTo, recv, recvFrom)
4import Network.Socket.ByteString 5import Network.Socket.ByteString
5import Network.Torrent \ No newline at end of file 6
7import Network.Torrent
8
9import System.IO.MMap.Fixed as F
10import System.IO.MMap.Fixed as Fixed \ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 7a0c156b..b662ed9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
1dist 1dist
2cabal-dev 2cabal-dev
3tmp
3*.aux 4*.aux
4*.eventlog 5*.eventlog
5*.hp 6*.hp
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..b5c055dc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
1.PHONY: clean
2
3clean:
4 rm -r tmp \ No newline at end of file
diff --git a/bittorrent.cabal b/bittorrent.cabal
index e7b2e33d..f99405fa 100644
--- a/bittorrent.cabal
+++ b/bittorrent.cabal
@@ -42,6 +42,7 @@ library
42 , Network.BitTorrent.Exchange 42 , Network.BitTorrent.Exchange
43 43
44 other-modules: Network.BitTorrent.Internal 44 other-modules: Network.BitTorrent.Internal
45 , System.IO.MMap.Fixed
45 46
46 if flag(testing) 47 if flag(testing)
47 exposed-modules: Network.BitTorrent.Exchange.Protocol 48 exposed-modules: Network.BitTorrent.Exchange.Protocol
@@ -56,12 +57,12 @@ library
56 build-depends: 57 build-depends:
57 base == 4.* 58 base == 4.*
58 59
59 -- Control packages 60 -- Control
60 , mtl 61 , mtl
61 , resourcet 62 , resourcet
62 , lens 63 , lens
63 64
64 -- Concurrency packages 65 -- Concurrency
65 , SafeSemaphore 66 , SafeSemaphore
66 , BoundedChan >= 1.0.1.0 67 , BoundedChan >= 1.0.1.0
67 , stm >= 2.4 68 , stm >= 2.4
@@ -71,50 +72,58 @@ library
71 , network-conduit == 1.* 72 , network-conduit == 1.*
72 , cereal-conduit >= 0.5 73 , cereal-conduit >= 0.5
73 74
74 -- Data packages 75 -- Data
75 , array >= 0.4 76 , array >= 0.4
76 , bytestring 77 , bytestring >= 0.10
77 -- >= 0.10.2 78
78 , containers >= 0.4 79 , containers >= 0.4
79 , intset >= 0.1 80 , intset >= 0.1
81 , IntervalMap >= 0.3
82
80 , text >= 0.11.0 83 , text >= 0.11.0
81 , vector 84 , vector
82 85
83 -- Encoding/Serialization packages 86 -- Encoding/Serialization
84 , bencoding >= 0.1 87 , bencoding >= 0.1
85 , cereal >= 0.3 88 , cereal >= 0.3
86 , urlencoded >= 0.4 89 , urlencoded >= 0.4
87 90
88 -- Time packages 91 -- Time
89 , time >= 0.1 92 , time >= 0.1
90 , old-locale >= 1.0 93 , old-locale >= 1.0
91 94
92 -- Network packages 95 -- Network
93 , network >= 2.4 96 , network >= 2.4
94 , HTTP >= 4000.2 97 , HTTP >= 4000.2
95 , krpc 98 , krpc
96 99
100 -- System
101 , filepath >= 1
102 , mmap >= 0.5.2
103
97 -- Misc 104 -- Misc
98 , data-default 105 , data-default
99 , cryptohash 106 , cryptohash
100 , filepath >= 1
101 , bits-atomic >= 0.1
102 , pretty 107 , pretty
103 108
109 , bits-atomic >= 0.1
110
104 extensions: PatternGuards 111 extensions: PatternGuards
105 hs-source-dirs: src 112 hs-source-dirs: src
106 if flag(testing) 113 if flag(testing)
107 cpp-options: -DTESTING 114 cpp-options: -DTESTING
108 ghc-options: -Wall 115 ghc-options: -Wall
109 116
110executable exsample 117
118
119executable example
111 main-is: Main.hs 120 main-is: Main.hs
112 hs-source-dirs: exsamples 121 hs-source-dirs: examples
113 build-depends: base == 4.* 122 build-depends: base == 4.*
114 , bittorrent 123 , bittorrent
115 , mtl 124 , mtl
116 125
117 ghc-options: -O2 -rtsopts -threaded 126 ghc-options: -O2 -rtsopts -threaded
118-- -threaded -rtsopts 127-- -threaded -rtsopts
119-- -eventlog 128-- -eventlog
120-- ghc-prof-options: -prof -auto-all -caf-all 129-- ghc-prof-options: -prof -auto-all -caf-all
@@ -123,6 +132,7 @@ executable exsample
123 buildable: False 132 buildable: False
124 133
125 134
135
126test-suite info-hash 136test-suite info-hash
127 type: exitcode-stdio-1.0 137 type: exitcode-stdio-1.0
128 main-is: InfoHash.hs 138 main-is: InfoHash.hs
@@ -143,6 +153,23 @@ test-suite info-hash
143 153
144 154
145 155
156test-suite mmap
157 type: exitcode-stdio-1.0
158 main-is: MMap.hs
159 build-depends: base == 4.*
160 , bytestring
161 , directory
162
163 , HUnit
164 , test-framework
165 , test-framework-hunit
166
167 , mmap-fixed
168
169 hs-source-dirs: tests
170
171
172
146test-suite properties 173test-suite properties
147 type: exitcode-stdio-1.0 174 type: exitcode-stdio-1.0
148 main-is: Main.hs 175 main-is: Main.hs
diff --git a/src/System/IO/MMap/Fixed.hs b/src/System/IO/MMap/Fixed.hs
new file mode 100644
index 00000000..9d2e354f
--- /dev/null
+++ b/src/System/IO/MMap/Fixed.hs
@@ -0,0 +1,203 @@
1-- TODO pprint
2-- TODO see if this IntervalMap is overkill: Interval dataty have 4 constrs
3-- TODO clarify lifetime in docs
4-- TODO use madvise
5-- TODO unmap selected interval
6-- TODO tests
7-- TODO benchmarks
8-- TODO unmap overlapped regions
9-- [A] TODO lazy mapping for 32 bit arch;
10-- we need tricky algorithm and a lot of perf tests
11-- TODO use memmove in write bytes
12-- TODO write elem, write byte, read byte
13-- |
14-- Copyright : (c) Sam T. 2013
15-- License : MIT
16-- Maintainer : pxqr.sta@gmail.com
17-- Stability : experimental
18-- Portability : portable
19--
20-- This library provides mechanism to mmap files to fixed address
21-- with fine-grained control. Hovewer, instead of using MAP_FIXED we
22-- create our own address space upon virtual address space. If you
23-- would like you could call this space as "fixed address space".
24--
25-- This solves a few problems:
26--
27-- * Page already in use. If you mmap one file at 0..x addresses and
28-- want to map second file to x..y addresses using MAP_FIXED you
29-- can get in troubles: page might be mapped already. Raw call to
30-- mmap will silently unmap x..y addresses and then mmap our second
31-- file. So here we have extra unmap we would like to avoid.
32--
33-- * Page boundaries. If you mmap one file at x..x+1 you could
34-- not map next file to say addresses x+1..x+2.
35--
36-- Internally we make ordinary call to mmap to map a file and then
37-- using /interval map/ we map fixed address space to virtual
38-- address space. It takes TODO time in TODO cases.
39--
40-- Basically this library could be used when we need coalesce
41-- several files in arbitrary way. We could map at any position as
42-- long as offset + size fit in 'Int'.
43--
44-- For other details see:
45--
46-- > http://hackage.haskell.org/package/mmap
47-- > man mmap
48--
49module System.IO.MMap.Fixed
50 ( -- * Intervals
51 FixedOffset, FileOffset, FixedInterval, FileInterval
52 , interval, fileInterval
53
54 -- * Construction
55 , Fixed, Bytes
56 , System.IO.MMap.Fixed.empty, insertTo
57 , coalesceFiles
58
59 -- ** Specialized 'insertTo'
60 , mmapTo, mallocTo
61
62 -- * Query
63 , upperAddr
64
65 -- * Access
66 , viewBytes, readBytes, writeBytes
67 , readElem, writeElem
68 ) where
69
70import Data.ByteString.Lazy as Lazy
71import Data.ByteString.Lazy.Internal as Lazy
72import Data.ByteString.Internal as B
73import Data.List as L
74import Data.Int
75import Data.IntervalMap.Strict as M
76import Data.IntervalMap.Interval
77import System.IO.MMap
78import Foreign
79
80
81type FixedOffset = Int
82type FileOffset = Int64
83type Size = Int
84
85
86type FileInterval = (FileOffset, Size)
87type FixedInterval = Interval FixedOffset
88
89
90interval :: FixedOffset -> Size -> FixedInterval
91interval off s = IntervalCO off (off + fromIntegral (max 0 s))
92{-# INLINE interval #-}
93
94fileInterval :: FileOffset -> Size -> FileInterval
95fileInterval off s = (off, s)
96{-# INLINE fileInterval #-}
97
98intervalSize :: FixedInterval -> Size
99intervalSize i = upperBound i - lowerBound i
100{-# INLINE intervalSize #-}
101
102
103type Bytes = (ForeignPtr Word8, Size)
104
105type FixedMap = IntervalMap FixedOffset Bytes
106
107newtype Fixed = Fixed { imap :: FixedMap }
108
109instance Show Fixed where
110 show = show . M.toList . imap
111
112
113mapIM :: (FixedMap -> FixedMap) -> Fixed -> Fixed
114mapIM f s = s { imap = f (imap s) }
115
116empty :: Fixed
117empty = Fixed M.empty
118
119coalesceFiles :: [(FilePath, Int)] -> IO Fixed
120coalesceFiles = go 0 System.IO.MMap.Fixed.empty
121 where
122 go _ s [] = return s
123 go offset s ((path, bsize) : xs) = do
124 s' <- mmapTo path (0, bsize) offset s
125 go (offset + bsize) s' xs
126
127upperAddr :: Fixed -> FixedOffset
128upperAddr = upperBound . fst . findLast . imap
129
130insertTo :: FixedInterval -> Bytes -> Fixed -> Fixed
131insertTo fi mm = mapIM (M.insert fi mm)
132{-# INLINE insertTo #-}
133
134mmapTo :: FilePath -> FileInterval -> FixedOffset -> Fixed -> IO Fixed
135mmapTo path mrange to s = do
136 (fptr, offset, fsize) <- mmapFileForeignPtr path ReadWriteEx (Just mrange)
137
138 let fixed = interval to fsize
139 let mmaped = (fptr, offset)
140
141 return $ insertTo fixed mmaped s
142
143mallocTo :: FixedInterval -> Fixed -> IO Fixed
144mallocTo fi s = do
145 let bsize = intervalSize fi
146 fptr <- mallocForeignPtrBytes bsize
147 return (insertTo fi (fptr, 0) s)
148
149-- | Note: this is unsafe operation.
150viewBytes :: FixedInterval -> Fixed -> Lazy.ByteString
151viewBytes fi s = fromChunks $ L.map mk $ (imap s `intersecting` fi)
152 where
153 mk (i, (fptr, offset)) =
154 let dropB = max 0 (lowerBound fi - lowerBound i)
155 dropT = max 0 (upperBound i - upperBound fi)
156 bsize = intervalSize i - (dropT + dropB)
157 in fromForeignPtr fptr (offset + dropB) bsize
158
159
160readBytes :: FixedInterval -> Fixed -> IO Lazy.ByteString
161readBytes fi s = let c = Lazy.copy (viewBytes fi s) in mkCopy c >> return c
162{-# INLINE readBytes #-}
163
164writeBytes :: FixedInterval -> Lazy.ByteString -> Fixed -> IO ()
165writeBytes fi bs s = bscpy (viewBytes fi s) bs
166{-# INLINE writeBytes #-}
167
168-- | Note: this operation takes O(log(files count)) time, if possible
169-- use readBytes.
170readElem :: Storable a => Fixed -> FixedOffset -> IO a
171readElem s offset = go undefined
172 where
173 go :: Storable a => a -> IO a
174 go dont_touch = do
175 let bsize = sizeOf dont_touch
176 let PS fptr off _ = Lazy.toStrict (viewBytes (interval offset bsize) s)
177 withForeignPtr fptr $ \ ptr -> peekByteOff ptr off
178
179writeElem :: Storable a => Fixed -> FixedOffset -> a -> IO ()
180writeElem s offset x = do
181 let bsize = sizeOf x
182 let PS fptr off _ = Lazy.toStrict (viewBytes (interval offset bsize) s)
183 withForeignPtr fptr $ \ptr -> pokeByteOff ptr off x
184
185
186mkCopy :: Lazy.ByteString -> IO ()
187mkCopy Empty = return ()
188mkCopy (Chunk _ x) = mkCopy x
189
190bscpy :: Lazy.ByteString -> Lazy.ByteString -> IO ()
191bscpy (PS _ _ 0 `Chunk` dest_rest) src = bscpy dest_rest src
192bscpy dest (PS _ _ 0 `Chunk` src_rest) = bscpy dest src_rest
193bscpy (PS dest_fptr dest_off dest_size `Chunk` dest_rest)
194 (PS src_fptr src_off src_size `Chunk` src_rest)
195 = do let csize = min dest_size src_size
196 withForeignPtr dest_fptr $ \dest_ptr ->
197 withForeignPtr src_fptr $ \src_ptr ->
198 memcpy (dest_ptr `advancePtr` dest_off)
199 (src_ptr `advancePtr` src_off)
200 (fromIntegral csize) -- TODO memmove?
201 bscpy (PS dest_fptr (dest_off + csize) (dest_size - csize) `Chunk` dest_rest)
202 (PS src_fptr (src_off + csize) (src_size - csize) `Chunk` src_rest)
203bscpy _ _ = return () \ No newline at end of file
diff --git a/tests/MMap.hs b/tests/MMap.hs
new file mode 100644
index 00000000..a4be95f0
--- /dev/null
+++ b/tests/MMap.hs
@@ -0,0 +1,45 @@
1{-# LANGUAGE OverloadedStrings #-}
2module Main (main) where
3
4import Test.HUnit
5import Test.Framework
6import Test.Framework.Providers.HUnit
7import Data.Word
8
9import System.Directory
10import System.IO.MMap.Fixed
11
12
13boundaryTest = do
14 f <- mallocTo (interval 0 1) empty
15 f <- mallocTo (interval 1 2) f
16 writeElem f 0 (1 :: Word8)
17 writeElem f 1 (2 :: Word8)
18 bs <- readBytes (interval 0 2) f
19 "\x1\x2" @=? bs
20
21mmapSingle = do
22 f <- mmapTo "single.test" (10, 5) 5 empty
23 writeBytes (interval 5 5) "abcde" f
24 bs <- readBytes (interval 5 5) f
25 "abcde" @=? bs
26
27coalesceTest = do
28 f <- mmapTo "a.test" (0, 1) 10 empty
29 f <- mmapTo "bc.test" (0, 2) 12 f
30 f <- mmapTo "c.test" (0, 1) 13 f
31 writeBytes (interval 10 4) "abcd" f
32 bs <- readBytes (interval 10 4) f
33 "abcd" @=? bs
34
35main :: IO ()
36main = do
37 let tmpdir = "tmp"
38 createDirectoryIfMissing True tmpdir
39 setCurrentDirectory tmpdir
40
41 defaultMain
42 [ testCase "boudary" boundaryTest
43 , testCase "single" mmapSingle
44 , testCase "coalesce" coalesceTest
45 ] \ No newline at end of file