@@ -5,8 +5,10 @@ module Database.LSMTree.Internal.Merge (
5
5
Merge (.. )
6
6
, Level (.. )
7
7
, Mappend
8
+ , MergeState (.. )
8
9
, new
9
- , close
10
+ , addReference
11
+ , removeReference
10
12
, complete
11
13
, stepsToCompletion
12
14
, stepsToCompletionCounted
@@ -18,10 +20,12 @@ import Control.Exception (assert)
18
20
import Control.Monad (when )
19
21
import Control.Monad.Class.MonadST (MonadST )
20
22
import Control.Monad.Class.MonadSTM (MonadSTM (.. ))
21
- import Control.Monad.Class.MonadThrow (MonadCatch , MonadThrow (.. ))
23
+ import Control.Monad.Class.MonadThrow (MonadCatch , MonadMask (.. ),
24
+ MonadThrow (.. ))
22
25
import Control.Monad.Fix (MonadFix )
23
- import Control.Monad.Primitive (PrimState , RealWorld )
24
- import Control.RefCount (RefCount (.. ))
26
+ import Control.Monad.Primitive (PrimMonad , PrimState , RealWorld )
27
+ import Control.RefCount (RefCounter )
28
+ import qualified Control.RefCount as RC
25
29
import Data.Coerce (coerce )
26
30
import Data.Primitive.MutVar
27
31
import Data.Traversable (for )
@@ -37,6 +41,7 @@ import qualified Database.LSMTree.Internal.RunReader as Reader
37
41
import Database.LSMTree.Internal.RunReaders (Readers )
38
42
import qualified Database.LSMTree.Internal.RunReaders as Readers
39
43
import Database.LSMTree.Internal.Serialise
44
+ import GHC.Stack (HasCallStack )
40
45
import qualified System.FS.API as FS
41
46
import System.FS.API (HasFS )
42
47
import System.FS.BlockIO.API (HasBlockIO )
@@ -45,23 +50,34 @@ import System.FS.BlockIO.API (HasBlockIO)
45
50
--
46
51
-- Since we always resolve all entries of the same key in one go, there is no
47
52
-- need to store incompletely-resolved entries.
48
- --
49
- -- TODO: Reference counting will have to be done somewhere, either here or in
50
- -- the layer above.
51
53
data Merge m h = Merge {
52
- mergeLevel :: ! Level
53
- , mergeMappend :: ! Mappend
54
- , mergeReaders :: {-# UNPACK #-} ! (Readers m (FS. Handle h ))
55
- , mergeBuilder :: ! (RunBuilder (PrimState m ) (FS. Handle h ))
54
+ mergeLevel :: ! Level
55
+ , mergeMappend :: ! Mappend
56
+ , mergeReaders :: {-# UNPACK #-} ! (Readers m (FS. Handle h ))
57
+ , mergeBuilder :: ! (RunBuilder (PrimState m ) (FS. Handle h ))
56
58
-- | The caching policy to use for the Run in the 'MergeComplete'.
57
- , mergeCaching :: ! RunDataCaching
59
+ , mergeCaching :: ! RunDataCaching
58
60
-- | The result of the latest call to 'steps'. This is used to determine
59
61
-- whether a merge can be 'complete'd.
60
- , mergeLastStepResult :: ! (MutVar (PrimState m ) StepResult )
61
- , mergeHasFS :: ! (HasFS m h )
62
- , mergeHasBlockIO :: ! (HasBlockIO m h )
62
+ , mergeState :: ! (MutVar (PrimState m ) MergeState )
63
+ , mergeRefCounter :: ! (RefCounter m )
64
+ , mergeHasFS :: ! (HasFS m h )
65
+ , mergeHasBlockIO :: ! (HasBlockIO m h )
63
66
}
64
67
68
+ -- | The current state of the merge.
69
+ data MergeState =
70
+ -- | There is still merging work to be done
71
+ Merging
72
+ -- | There is no more merging work to be done, but the merge still has to be
73
+ -- completed to yield a new run.
74
+ | MergingDone
75
+ -- | A run was yielded as the result of a merge. The merge is implicitly
76
+ -- closed.
77
+ | Completed
78
+ -- | The merge was closed before it was completed.
79
+ | Closed
80
+
65
81
data Level = MidLevel | LastLevel
66
82
deriving stock (Eq , Show )
67
83
@@ -80,7 +96,7 @@ type Mappend = SerialisedValue -> SerialisedValue -> SerialisedValue
80
96
-- | Returns 'Nothing' if no input 'Run' contains any entries.
81
97
-- The list of runs should be sorted from new to old.
82
98
new ::
83
- (MonadCatch m , MonadSTM m , MonadST m )
99
+ (MonadCatch m , MonadSTM m , MonadST m , MonadFix m )
84
100
=> HasFS m h
85
101
-> HasBlockIO m h
86
102
-> RunDataCaching
@@ -97,31 +113,65 @@ new fs hbio mergeCaching alloc mergeLevel mergeMappend targetPaths runs = do
97
113
-- calculate upper bounds based on input runs
98
114
let numEntries = coerce (sum @ V. Vector @ Int ) (fmap Run. runNumEntries runs)
99
115
mergeBuilder <- Builder. new fs targetPaths numEntries alloc
100
- mergeLastStepResult <- newMutVar $! MergeInProgress
116
+ mergeState <- newMutVar $! Merging
117
+ mergeRefCounter <-
118
+ RC. mkRefCounter1 (Just $! finaliser fs hbio mergeState mergeBuilder mergeReaders)
101
119
return Merge {
102
120
mergeHasFS = fs
103
121
, mergeHasBlockIO = hbio
104
122
, ..
105
123
}
106
124
107
- {-# SPECIALISE close :: Merge IO (FS.Handle h) -> IO () #-}
108
- -- | This function should be called when discarding a 'Merge' before it
109
- -- was done (i.e. returned 'MergeComplete'). This removes the incomplete files
110
- -- created for the new run so far and avoids leaking file handles.
125
+ {-# SPECIALISE addReference :: Merge IO h -> IO () #-}
126
+ addReference :: (HasCallStack , PrimMonad m ) => Merge m h -> m ()
127
+ addReference Merge {.. } = RC. addReference mergeRefCounter
128
+
129
+ {-# SPECIALISE removeReference :: Merge IO h -> IO () #-}
130
+ removeReference :: (HasCallStack , PrimMonad m , MonadMask m ) => Merge m h -> m ()
131
+ removeReference Merge {.. } = RC. removeReference mergeRefCounter
132
+
133
+ {-# SPECIALISE finaliser :: HasFS IO h -> HasBlockIO IO h -> MutVar RealWorld MergeState -> RunBuilder RealWorld (FS.Handle h) -> Readers IO (FS.Handle h) -> IO () #-}
134
+ -- | Closes the underlying builder and readers.
111
135
--
112
- -- Once it has been called, do not use the 'Merge' any more!
113
- close :: (MonadFix m , MonadSTM m , MonadST m ) => Merge m h -> m ()
114
- close Merge {.. } = do
115
- Builder. close mergeHasFS mergeBuilder
116
- Readers. close mergeHasFS mergeHasBlockIO mergeReaders
136
+ -- This function is idempotent. Technically, this is not necessary because the
137
+ -- finaliser is going to run only once, but it is a nice property for
138
+ -- @close@-like functions to be idempotent.
139
+ finaliser ::
140
+ (MonadFix m , MonadSTM m , MonadST m )
141
+ => HasFS m h
142
+ -> HasBlockIO m h
143
+ -> MutVar (PrimState m ) MergeState
144
+ -> RunBuilder (PrimState m ) (FS. Handle h )
145
+ -> Readers m (FS. Handle h )
146
+ -> m ()
147
+ finaliser hfs hbio var b rs = do
148
+ st <- readMutVar var
149
+ let shouldClose = case st of
150
+ Merging -> True
151
+ MergingDone -> True
152
+ Completed -> False
153
+ Closed -> False
154
+ when shouldClose $ do
155
+ Builder. close hfs b
156
+ Readers. close hfs hbio rs
157
+ writeMutVar var $! Closed
117
158
118
159
{-# SPECIALISE complete ::
119
160
Merge IO h
120
161
-> IO (Run IO (FS.Handle h)) #-}
121
162
-- | Complete a 'Merge', returning a new 'Run' as the result of merging the
122
- -- input runs. This function will /not/ do any merging work if there is any
123
- -- remaining. That is, if not enough 'steps' were performed to exhaust the input
124
- -- 'Readers', this function will throw an error.
163
+ -- input runs.
164
+ --
165
+ -- The resulting run has the same reference count as the input 'Merge'. The
166
+ -- 'Merge' does not have to be closed afterwards, since it is closed implicitly
167
+ -- by 'complete'.
168
+ --
169
+ -- This function will /not/ do any merging work if there is any remaining. That
170
+ -- is, if not enough 'steps' were performed to exhaust the input 'Readers', this
171
+ -- function will throw an error.
172
+ --
173
+ -- Returns an error if the merge was not yet done, if it was already completed
174
+ -- before, or if it was already closed.
125
175
--
126
176
-- Note: this function creates new 'Run' resources, so it is recommended to run
127
177
-- this function with async exceptions masked. Otherwise, these resources can
@@ -131,11 +181,22 @@ complete ::
131
181
=> Merge m h
132
182
-> m (Run m (FS. Handle h ))
133
183
complete Merge {.. } = do
134
- readMutVar mergeLastStepResult >>= \ case
135
- MergeInProgress -> error " complete: Merge is not yet completed!"
136
- MergeComplete -> do
137
- Run. fromMutable mergeHasFS mergeHasBlockIO mergeCaching
138
- (RefCount 1 ) mergeBuilder
184
+ readMutVar mergeState >>= \ case
185
+ Merging -> error " complete: Merge is not done"
186
+ MergingDone -> do
187
+ -- Since access to a merge /should/ be sequentialised, we can assume
188
+ -- that the ref count has not changed between this read and the use of
189
+ -- fromMutable.
190
+ --
191
+ -- TODO: alternatively, the mergeRefCounter could be reused as the
192
+ -- reference counter for the output run.
193
+ n <- RC. readRefCount mergeRefCounter
194
+ r <- Run. fromMutable mergeHasFS mergeHasBlockIO mergeCaching
195
+ n mergeBuilder
196
+ writeMutVar mergeState $! Completed
197
+ pure r
198
+ Completed -> error " complete: Merge is already completed"
199
+ Closed -> error " complete: Merge is closed"
139
200
140
201
{-# SPECIALISE stepsToCompletion ::
141
202
Merge IO h
@@ -194,6 +255,8 @@ stepsInvariant requestedSteps = \case
194
255
--
195
256
-- Returns the number of input entries read, which is guaranteed to be at least
196
257
-- as many as requested (unless the merge is complete).
258
+ --
259
+ -- Returns an error if the merge was already completed or closed.
197
260
steps ::
198
261
forall h m .
199
262
(MonadCatch m , MonadSTM m , MonadST m )
@@ -207,9 +270,11 @@ steps Merge {..} requestedSteps = assertStepsInvariant <$> do
207
270
-- clear whether our (upcoming) implementation of scheduled merges is going
208
271
-- to satisfy this precondition when it calls @steps@, so for now we do the
209
272
-- check.
210
- readMutVar mergeLastStepResult >>= \ case
211
- MergeComplete -> pure (0 , MergeComplete )
212
- MergeInProgress -> go 0
273
+ readMutVar mergeState >>= \ case
274
+ Merging -> go 0
275
+ MergingDone -> pure (0 , MergeComplete )
276
+ Completed -> error " steps: Merge is completed"
277
+ Closed -> error " steps: Merge is closed"
213
278
where
214
279
assertStepsInvariant res = assert (stepsInvariant requestedSteps res) res
215
280
@@ -228,7 +293,7 @@ steps Merge {..} requestedSteps = assertStepsInvariant <$> do
228
293
Readers. Drained -> do
229
294
-- no future entries, no previous entry to resolve, just write!
230
295
writeReaderEntry fs mergeLevel mergeBuilder key entry
231
- writeMutVar mergeLastStepResult $! MergeComplete
296
+ writeMutVar mergeState $! MergingDone
232
297
pure (n + 1 , MergeComplete )
233
298
234
299
handleEntry ! n ! key (Reader. Entry (Mupdate v)) =
@@ -267,15 +332,15 @@ steps Merge {..} requestedSteps = assertStepsInvariant <$> do
267
332
dropRemaining (n + 1 ) key
268
333
Readers. Drained -> do
269
334
writeSerialisedEntry fs mergeLevel mergeBuilder key resolved
270
- writeMutVar mergeLastStepResult $! MergeComplete
335
+ writeMutVar mergeState $! MergingDone
271
336
pure (n + 1 , MergeComplete )
272
337
273
338
dropRemaining ! n ! key = do
274
339
(dropped, hasMore) <- Readers. dropWhileKey fs hbio mergeReaders key
275
340
case hasMore of
276
341
Readers. HasMore -> go (n + dropped)
277
342
Readers. Drained -> do
278
- writeMutVar mergeLastStepResult $! MergeComplete
343
+ writeMutVar mergeState $! MergingDone
279
344
pure (n + dropped, MergeComplete )
280
345
281
346
{-# SPECIALISE writeReaderEntry ::
0 commit comments