packages feed

dfinity-radix-tree 0.2.1 → 0.3.0

raw patch · 8 files changed

+338/−214 lines, 8 filesdep +concurrent-extraPVP ok

version bump matches the API change (PVP)

Dependencies added: concurrent-extra

API changes (from Hackage documentation)

- Network.DFINITY.RadixTree: lookupMerkleizedRadixTree :: RadixDatabase m database => ByteString -> RadixTree database -> m (Maybe (ByteString, RadixTree database))
- Network.DFINITY.RadixTree: lookupNonMerkleizedRadixTree :: RadixDatabase m database => ByteString -> RadixTree database -> m (Maybe (ByteString, RadixTree database))
- Network.DFINITY.RadixTree: sinkMerkleizedRadixTree :: MonadResource m => RadixDatabase (ConduitM ByteString () m) database => RadixRoot -> BoundedChan RadixRoot -> RadixTree database -> ConduitM ByteString () m (Either [RadixRoot] (RadixTree database))
- Network.DFINITY.RadixTree: sourceMerkleizedRadixTree :: MonadResource m => RadixDatabase (ConduitM () ByteString m) database => [Bool] -> Int -> BoundedChan RadixRoot -> RadixTree database -> ConduitM () ByteString m ()
- Network.DFINITY.RadixTree: subtreeRadixTree :: RadixDatabase m database => RadixRoot -> RadixTree database -> m (RadixTree database)
+ Network.DFINITY.RadixTree.Conduit: sinkRadixTree :: forall m database. MonadResource m => RadixDatabase (ConduitT ByteString Void m) database => RadixRoot -> BoundedChan RadixRoot -> RadixTree database -> RWLock -> ConduitT ByteString Void m (Either [RadixRoot] (RadixTree database))
+ Network.DFINITY.RadixTree.Conduit: sourceRadixTree :: forall m database. MonadResource m => RadixDatabase (ConduitT () ByteString m) database => [Bool] -> Int -> BoundedChan RadixRoot -> RadixTree database -> RWLock -> ConduitT () ByteString m ()

Files

+ .travis.yml view
@@ -0,0 +1,10 @@+language: haskell++addons:+  apt:+    packages:+    - libleveldb-dev++ghc:+  - 8.2+  - 8.4
CHANGELOG.md view
@@ -1,3 +1,9 @@+0.3.0 Enzo Haussecker <enzo@dfinity.org> Thu Aug 02 2018++ * Add thread safety to parallel download combinators+ * Separate impure code from base module+ * Remove deprecated functions+ 0.2.1 Enzo Haussecker <enzo@dfinity.org> Wed Jul 25 2018   * Fix bug related to temporary reference creation
dfinity-radix-tree.cabal view
@@ -1,5 +1,5 @@ Name:               dfinity-radix-tree-Version:            0.2.1+Version:            0.3.0 Synopsis:           A generic data integrity layer. Description:    This library allows you to construct a Merkle tree on top of any underlying@@ -27,6 +27,7 @@       base16-bytestring,       bloomfilter,       bytestring,+      concurrent-extra,       conduit,       containers,       cryptohash-sha256,@@ -44,6 +45,7 @@       Haskell2010    Exposed-Modules:       Network.DFINITY.RadixTree+      Network.DFINITY.RadixTree.Conduit    GHC-Options:       -O2       -Wall@@ -54,6 +56,7 @@       Network.DFINITY.RadixTree.Bits       Network.DFINITY.RadixTree.Bloom       Network.DFINITY.RadixTree.Lenses+      Network.DFINITY.RadixTree.Lock       Network.DFINITY.RadixTree.Memory       Network.DFINITY.RadixTree.Serialise       Network.DFINITY.RadixTree.Types
src/Network/DFINITY/RadixTree.hs view
@@ -1,10 +1,10 @@-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-}  {-# OPTIONS -Wall #-} {-# OPTIONS -Werror=incomplete-patterns #-}+{-# OPTIONS -fno-warn-unused-top-binds #-}  -- | -- Module     : Network.DFINITY.RadixTree@@ -26,7 +26,6 @@     -- ** Create    , createRadixTree-   , subtreeRadixTree     -- ** Insert    , insertRadixTree@@ -39,47 +38,34 @@     -- ** Query    , lookupRadixTree-   , lookupMerkleizedRadixTree-   , lookupNonMerkleizedRadixTree     -- ** Test    , isEmptyRadixTree    , isValidRadixRoot -   -- ** Stream-   , sourceMerkleizedRadixTree-   , sinkMerkleizedRadixTree-    -- ** Debug    , contentsRadixTree    , contentsMerkleizedRadixTree    , contentsNonMerkleizedRadixTree+    , printRadixTree    , printMerkleizedRadixTree    , printNonMerkleizedRadixTree     ) where -import Codec.Serialise (deserialise, deserialiseOrFail)-import Control.Concurrent (forkIO, killThread)-import Control.Concurrent.BoundedChan (BoundedChan, readChan, tryWriteChan)-import Control.Concurrent.MVar (modifyMVar_, newMVar, readMVar) import Control.Exception (throw)-import Control.Monad (foldM, forM_, forever, void, when)+import Control.Monad (foldM, forM_) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Trans.Resource (MonadResource, ResourceT, allocate, release)-import Crypto.Hash.SHA256 (hash)+import Control.Monad.Trans.Resource (ResourceT) import Data.BloomFilter as Bloom (elem, insert, insertList) import Data.Bool (bool)-import Data.ByteString.Char8 as Byte (ByteString, take)-import Data.ByteString.Lazy (fromStrict)-import Data.ByteString.Short (fromShort, toShort)-import Data.Conduit (ConduitM, await, yield)+import Data.ByteString (ByteString)+import Data.ByteString.Short (fromShort) import Data.Default.Class (def)-import Data.List as List (delete, foldl', null) import Data.List.NonEmpty (NonEmpty(..), fromList)-import Data.LruCache as LRU (empty, insert, lookup)-import Data.Map.Strict as Map ((!), delete, empty, insert, keys, lookup, member, null, singleton)+import Data.LruCache as LRU (empty)+import Data.Map.Strict as Map (empty) import Data.Maybe (fromJust, isJust, isNothing, listToMaybe) import Data.Tuple (swap) import Database.LevelDB (DB)@@ -124,26 +110,6 @@       cache = LRU.empty cacheSize  -- |--- Create a radix tree from a radix tree.-subtreeRadixTree-   :: RadixDatabase m database-   => RadixRoot -- ^ State root.-   -> RadixTree database -- ^ Radix tree.-   -> m (RadixTree database)-{-# SPECIALISE subtreeRadixTree-   :: RadixRoot-   -> RadixTree DB-   -> ResourceT IO (RadixTree DB) #-}-subtreeRadixTree root RadixTree {..} = do-   result <- loadCold root cache _radixDatabase-   case result of-      Nothing -> throw $ StateRootDoesNotExist root-      _ -> pure $ RadixTree bloom _radixBloomSize Map.empty cache _radixCacheSize root _radixDatabase 0 root-      where-      bloom = emptyRadixBloom _radixBloomSize-      cache = LRU.empty _radixCacheSize---- | -- Check if a radix tree is empty. isEmptyRadixTree    :: RadixTree database -- ^ Radix tree.@@ -203,10 +169,10 @@             let prefixes' = prefix:prefixes             let key' = drop n key             -- Check the termination criteria.-            let residue = not $ List.null overflow+            let residue = not $ null overflow             let bit = head key'             let child = bool _radixLeft _radixRight bit-            if List.null key' || residue || isNothing child+            if null key' || residue || isNothing child             then pure $ Right (fromList roots', fromList nodes', fromList prefixes', overflow, key', cache')             else do                -- Recurse.@@ -246,7 +212,7 @@       loadHot _radixRoot _radixBuffer _radixCache _radixDatabase  -- |--- Insert a key and value into a radix tree.+-- Insert a value into a radix tree. insertRadixTree    :: RadixDatabase m database    => ByteString -- ^ Key.@@ -285,7 +251,7 @@    prefix = createPrefix $ toBits key    node = setPrefix prefix $ Just value `setLeaf` def    root = createRootFromNonce _radixNonce-   bloom = Bloom.insert root _radixBloom+   bloom = insert root _radixBloom    nonce = _radixNonce + 1    buffer = storeHot root node _radixBuffer @@ -440,7 +406,7 @@ deleteRadixTreeNoChildrenNoParent (_, _, _, _, _, cache) tree@RadixTree {..} =    seq bloom $ setBloom bloom $ setBuffer buffer $ setCache cache $ setRoot state tree    where-   bloom = Bloom.insert defaultRoot _radixBloom+   bloom = insert defaultRoot _radixBloom    buffer = storeHot defaultRoot def _radixBuffer    state = defaultRoot @@ -529,23 +495,21 @@  -- | -- Lookup a value in a radix tree.-lookupRadixTree'+lookupRadixTree    :: RadixDatabase m database-   => (ByteString -> RadixTree database -> m (Either RadixError RadixSearchResult)) -- ^ Search algorithm.-   -> ByteString -- ^ Key.+   => ByteString -- ^ Key.    -> RadixTree database -- ^ Radix tree.    -> m (Maybe (ByteString, RadixTree database))-{-# SPECIALISE lookupRadixTree'-   :: (ByteString -> RadixTree DB -> ResourceT IO (Either RadixError RadixSearchResult))-   -> ByteString+{-# SPECIALISE lookupRadixTree+   :: ByteString    -> RadixTree DB    -> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}-lookupRadixTree' search key tree = do-   found <- search key tree+lookupRadixTree key tree = do+   found <- searchNonMerkleizedRadixTree key tree    case found of       Left err -> throw err       Right (_, RadixNode {..}:|_, _, prefixOverflow, keyOverflow, cache') ->-         if not $ List.null prefixOverflow && List.null keyOverflow+         if not $ null prefixOverflow && null keyOverflow          then pure Nothing          else pure $ do             value <- _radixLeaf@@ -553,42 +517,6 @@             pure (value, tree')  -- |--- A convenient alias for `lookupNonMerkleizedRadixTree`.-lookupRadixTree-   :: RadixDatabase m database-   => ByteString -- ^ Key.-   -> RadixTree database -- ^ Radix tree.-   -> m (Maybe (ByteString, RadixTree database))-{-# SPECIALISE lookupRadixTree-   :: ByteString-   -> RadixTree DB-   -> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}-lookupRadixTree = lookupNonMerkleizedRadixTree---- |--- Lookup a value in a Merkleized radix tree.-lookupMerkleizedRadixTree-   :: RadixDatabase m database-   => ByteString -- ^ Key.-   -> RadixTree database -- ^ Radix tree.-   -> m (Maybe (ByteString, RadixTree database))-{-# DEPRECATED lookupMerkleizedRadixTree "The function is will be removed in the next release. Please use 'lookupRadixTree' instead." #-}-lookupMerkleizedRadixTree = lookupRadixTree' searchMerkleizedRadixTree---- |--- Lookup a value in a non-Merkleized radix tree.-lookupNonMerkleizedRadixTree-   :: RadixDatabase m database-   => ByteString -- ^ Key.-   -> RadixTree database -- ^ Radix tree.-   -> m (Maybe (ByteString, RadixTree database))-{-# SPECIALISE lookupNonMerkleizedRadixTree-   :: ByteString-   -> RadixTree DB-   -> ResourceT IO (Maybe (ByteString, RadixTree DB)) #-}-lookupNonMerkleizedRadixTree = lookupRadixTree' searchNonMerkleizedRadixTree---- | -- Mask a node in a Merkleized radix tree. merkleSpoof    :: RadixRoot -- ^ State root.@@ -602,7 +530,7 @@       storeHot root $ test `setChild` Just mask $ node  -- |--- Merkleize a radix tree. This will flush the buffer to disk.+-- Merkleize a radix tree. This will flush the buffer to the database. merkleizeRadixTree    :: RadixDatabase m database    => RadixTree database-- ^ Radix tree.@@ -645,124 +573,6 @@                      (root'', cache''') <- loop right cache''                      let node' = setChildren (Just root', Just root'') node                      storeCold node' cache''' _radixDatabase---- |--- Create a conduit from a Merkleized radix tree.-sourceMerkleizedRadixTree-   :: MonadResource m-   => RadixDatabase (ConduitM () ByteString m) database-   => [Bool] -- ^ Bit mask.-   -> Int -- ^ LRU cache size in items.-   -> BoundedChan RadixRoot -- ^ Terminal state root producer.-   -> RadixTree database -- ^ Radix tree.-   -> ConduitM () ByteString m ()-{-# SPECIALISE sourceMerkleizedRadixTree-   :: [Bool]-   -> Int-   -> BoundedChan RadixRoot-   -> RadixTree DB-   -> ConduitM () ByteString (ResourceT IO) () #-}-sourceMerkleizedRadixTree mask cacheSize chan-   | cacheSize <= 0 = throw $ InvalidArgument "invalid LRU cache size"-   | otherwise = \ tree -> do-      cache <- liftIO $ newMVar $ LRU.empty cacheSize-      (,) action _ <- flip allocate killThread $ forkIO $ forever $ do-         root <- readChan chan-         modifyMVar_ cache $ pure . LRU.insert root ()-      loop cache tree []-      release action-      where-      loop cache tree@RadixTree {..} roots = do-         seen <- liftIO $ readMVar cache-         let roots' = _radixCheckpoint:roots-         if flip any roots' $ isJust . flip LRU.lookup seen-         then pure ()-         else do-            let key = fromShort _radixCheckpoint-            result <- load _radixDatabase key-            case result of-               Nothing -> pure ()-               Just bytes -> do-                  let RadixNode {..} = deserialise $ fromStrict bytes-                  let success = all id $ zipWith (==) mask $ toBits $ fromShort _radixCheckpoint-                  when success $ yield bytes-                  forM_ [_radixLeft, _radixRight] $ \ case-                     Nothing -> pure ()-                     Just root -> loop cache `flip` roots' $ setCheckpoint root tree---- |--- Create a Merkleized radix tree from a conduit.-sinkMerkleizedRadixTree-   :: MonadResource m-   => RadixDatabase (ConduitM ByteString () m) database-   => RadixRoot -- ^ Target state root.-   -> BoundedChan RadixRoot -- ^ Terminal state root consumer.-   -> RadixTree database -- ^ Radix tree.-   -> ConduitM ByteString () m (Either [RadixRoot] (RadixTree database))-{-# SPECIALISE sinkMerkleizedRadixTree-   :: RadixRoot-   -> BoundedChan RadixRoot-   -> RadixTree DB-   -> ConduitM ByteString () (ResourceT IO) (Either [RadixRoot] (RadixTree DB)) #-}-sinkMerkleizedRadixTree checkpoint chan tree@RadixTree {..} =-   loop1 Map.empty $ singleton checkpoint Nothing where-   loop1 = \ buffer want ->-      if Map.null want-      then pure $ Right $ setCheckpoint checkpoint $ setRoot checkpoint tree-      else await >>= \ case-         Nothing -> pure $ Left $ keys want-         Just bytes ->-            case deserialiseOrFail $ fromStrict bytes of-               Right RadixNode {..} -> do-                  let key = Byte.take 20 $ hash bytes-                  let root = toShort key-                  let wanted = member root want-                  exists <- if wanted-                     then pure False-                     else isJust <$> load _radixDatabase key-                  if exists-                  then loop1 buffer $ Map.delete root want-                  else do-                     children <- foldM step [] $ maybe id (:) _radixLeft $ maybe id (:) _radixRight []-                     let buffer' = Map.insert root (key, bytes, children) buffer-                     if not wanted-                     then loop1 buffer' want-                     else loop3 buffer' `uncurry` loop2 buffer' (want, []) root-               _ -> loop1 buffer want-      where-      step accum root = do-         valid <- isValidRadixRoot root tree-         if valid-         then pure accum-         else pure $ root:accum-   loop2 buffer accum@(want, candidates) root =-      case Map.lookup root buffer of-         Nothing -> accum-         Just (key, bytes, []) -> (want, (root, key, bytes):candidates)-         Just (_, _, children) ->-            let want' = foldr step want children-            in foldl' (loop2 buffer) (want', candidates) children-      where-      step = flip Map.insert $ Just root-   loop3 buffer want = \ case-      [] -> loop1 buffer want-      (root, key, bytes):candidates -> do-         store _radixDatabase key bytes-         let buffer' = Map.delete root buffer-         case want ! root of-            Nothing -> do-               let want' = Map.delete root want-               loop1 buffer' want'-            Just root' -> do-               let want' = Map.delete root want-               let (key', bytes', siblings') = buffer ! root'-               let children' = List.delete root siblings'-               if List.null children'-               then loop3 buffer' want' $ (root', key', bytes'):candidates-               else do-                  let buffer'' = Map.insert root' (key', bytes', children') buffer'-                  liftIO $ void $ tryWriteChan chan root-                  loop3 buffer'' want' candidates  -- | -- Get the contents of a radix tree.
+ src/Network/DFINITY/RadixTree/Conduit.hs view
@@ -0,0 +1,214 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-# OPTIONS -Wall #-}+{-# OPTIONS -Werror=incomplete-patterns #-}++-- |+-- Module     : Network.DFINITY.RadixTree.Conduit+-- Copyright  : 2018 DFINITY Stiftung+-- License    : GPL-3+-- Maintainer : Enzo Haussecker <enzo@dfinity.org>+-- Stability  : Stable+--+-- A parallel download protocol.+module Network.DFINITY.RadixTree.Conduit (++   -- ** Combinators+     sourceRadixTree+   , sinkRadixTree++   ) where++import Codec.Serialise (deserialise, deserialiseOrFail)+import Control.Concurrent (forkIO, killThread)+import Control.Concurrent.BoundedChan (BoundedChan, readChan, tryWriteChan)+import Control.Concurrent.MVar (modifyMVar_, newMVar, readMVar)+import Control.Concurrent.ReadWriteLock (RWLock)+import Control.Exception (throw)+import Control.Monad (foldM, forM_, forever, void, when)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.Resource (MonadResource, ResourceT, allocate, release)+import Crypto.Hash.SHA256 (hash)+import Data.ByteString.Lazy (fromStrict)+import Data.ByteString.Short (ShortByteString, fromShort, toShort)+import Data.ByteString.Char8 as Byte (ByteString, take)+import Data.Conduit (ConduitT, await, yield)+import Data.List as List (delete, null)+import Data.LruCache as LRU (empty, insert, lookup)+import Data.Map as Map (Map, (!), delete, empty, insert, keys, lookup, member, null, singleton)+import Data.Maybe (isJust)+import Data.Void (Void)+import Database.LevelDB (DB)++import Network.DFINITY.RadixTree.Bits+import Network.DFINITY.RadixTree.Lenses+import Network.DFINITY.RadixTree.Lock+import Network.DFINITY.RadixTree.Types++-- |+-- Create a conduit from a radix tree.+sourceRadixTree+   :: forall m database. MonadResource m+   => RadixDatabase (ConduitT () ByteString m) database+   => [Bool] -- ^ Bit mask.+   -> Int -- ^ LRU cache size in items.+   -> BoundedChan RadixRoot -- ^ Terminal state root producer.+   -> RadixTree database -- ^ Radix tree.+   -> RWLock -- ^ Database lock.+   -> ConduitT () ByteString m ()+{-# SPECIALISE sourceRadixTree+   :: [Bool]+   -> Int+   -> BoundedChan RadixRoot+   -> RadixTree DB+   -> RWLock+   -> ConduitT () ByteString (ResourceT IO) () #-}+sourceRadixTree mask cacheSize chan tree lock+   | cacheSize <= 0 = throw $ InvalidArgument "invalid LRU cache size"+   | otherwise = do+      cache <- liftIO $ newMVar $ LRU.empty cacheSize+      action <- fmap fst $ flip allocate killThread $ forkIO $ forever $ do+         root <- readChan chan+         modifyMVar_ cache $ pure . LRU.insert root ()+      loop cache tree []+      release action+      where+      loop cache subtree@RadixTree {..} accum = do+         let accum' = _radixCheckpoint:accum+         seen <- liftIO $ readMVar cache+         if flip any accum' $ isJust . flip LRU.lookup seen+         then pure ()+         else do+            let key = fromShort _radixCheckpoint+            result <- withReadLock lock $ load _radixDatabase key+            case result of+               Nothing -> pure ()+               Just bytes -> do+                  let RadixNode {..} = deserialise $ fromStrict bytes+                  let success = all id $ zipWith (==) mask $ toBits $ fromShort _radixCheckpoint+                  when success $ yield bytes+                  forM_ [_radixLeft, _radixRight] $ \ case+                     Nothing -> pure ()+                     Just root -> loop cache `flip` accum' $ setCheckpoint root subtree++-- |+-- Create a radix tree from a conduit.+sinkRadixTree+   :: forall m database. MonadResource m+   => RadixDatabase (ConduitT ByteString Void m) database+   => RadixRoot -- ^ Target state root.+   -> BoundedChan RadixRoot -- ^ Terminal state root consumer.+   -> RadixTree database -- ^ Radix tree.+   -> RWLock -- ^ Database lock.+   -> ConduitT ByteString Void m (Either [RadixRoot] (RadixTree database))+{-# SPECIALISE sinkRadixTree+   :: RadixRoot+   -> BoundedChan RadixRoot+   -> RadixTree DB+   -> RWLock+   -> ConduitT ByteString Void (ResourceT IO) (Either [RadixRoot] (RadixTree DB)) #-}+sinkRadixTree checkpoint chan tree@RadixTree {..} lock =+   loop1 Map.empty $ singleton checkpoint Nothing+   where++   -- Loop 1: The accumulation loop.+   loop1+      :: Map RadixRoot (ShortByteString, [RadixRoot])+      -> Map RadixRoot (Maybe RadixRoot)+      -> ConduitT ByteString Void m (Either [RadixRoot] (RadixTree database))+   loop1 buffer targets =+      -- Have we found all the subtrees?+      if Map.null targets+      then pure $ Right $ setCheckpoint checkpoint $ setRoot checkpoint tree+      else do+         -- Wait for a node.+         mval <- await+         case mval of+            Nothing -> pure $ Left $ keys targets+            Just bytes ->+               case deserialiseOrFail $ fromStrict bytes of+                  Left _ -> loop1 buffer targets+                  Right RadixNode {..} -> do+                     -- Does the node already exist in the database?+                     let key = Byte.take 20 $ hash bytes+                     let root = toShort key+                     let want = member root targets+                     exists <- if want+                        then pure False+                        else do+                           result <- withReadLock lock $ load _radixDatabase key+                           pure $ isJust result+                     if exists+                     then loop1 buffer $ Map.delete root targets+                     else do+                        -- Update the buffer to include the node.+                        children <- foldM step [] $ maybe id (:) _radixLeft $ maybe id (:) _radixRight []+                        let buffer' = Map.insert root (toShort bytes, children) buffer+                        -- Can we trace the node back to the target state root?+                        if want+                        then loop3 buffer' `uncurry` loop2 buffer' root (targets, [])+                        else loop1 buffer' targets+      where+      step accum root = do+         let key = fromShort root+         result <- withReadLock lock $ load _radixDatabase key+         if isJust result+         then pure accum+         else pure $ root:accum++   -- Loop 2: The aggregation loop.+   loop2+      :: Map RadixRoot (ShortByteString, [RadixRoot])+      -> RadixRoot+      -> (Map RadixRoot (Maybe RadixRoot), [(RadixRoot, ShortByteString)])+      -> (Map RadixRoot (Maybe RadixRoot), [(RadixRoot, ShortByteString)])+   loop2 buffer root accum@(targets, candidates) =+      -- Get the node from the buffer and analyze.+      case Map.lookup root buffer of+         Nothing -> accum+         Just (bytes, []) ->+            -- The node is now a candidate.+            let candidates' = (root, bytes):candidates+            in (targets, candidates')+         Just (_, children) ->+            -- The children are now targets.+            let targets' = foldr step1 targets children+            in foldl step2 (targets', candidates) children+      where+      step1 = flip Map.insert $ Just root+      step2 = flip $ loop2 buffer++   -- Loop 3: The write loop.+   loop3+      :: Map RadixRoot (ShortByteString, [RadixRoot])+      -> Map RadixRoot (Maybe RadixRoot)+      -> [(RadixRoot, ShortByteString)]+      -> ConduitT ByteString Void m (Either [RadixRoot] (RadixTree database))+   loop3 buffer targets = \ case+      [] -> loop1 buffer targets+      (root, bytes):candidates' -> do+         -- Write the node to the database.+         let key = fromShort root+         withWriteLock lock $ store _radixDatabase key $ fromShort bytes+         -- Remove all references to the node.+         let buffer' = Map.delete root buffer+         let targets' = Map.delete root targets+         -- Inspect the parent node.+         case targets ! root of+            Nothing -> loop3 buffer' targets' candidates'+            Just root' -> do+               let (bytes', siblings') = buffer ! root'+               let children' = List.delete root siblings'+               -- Has the sibling node been written to the database?+               if List.null children'+               then loop3 buffer' targets' $ (root', bytes'):candidates'+               else do+                  -- Announce a terminal state root.+                  liftIO $ void $ tryWriteChan chan root+                  -- Update the parent node.+                  let buffer'' = Map.insert root' (bytes', children') buffer'+                  loop3 buffer'' targets' candidates'
+ src/Network/DFINITY/RadixTree/Lock.hs view
@@ -0,0 +1,29 @@+{-# OPTIONS -Wall #-}++module Network.DFINITY.RadixTree.Lock+   ( withReadLock+   , withWriteLock+   ) where++import Control.Concurrent.ReadWriteLock (RWLock, acquireRead, acquireWrite, releaseRead, releaseWrite)+import Control.Monad.Trans.Resource (MonadResource, allocate, release)++withReadLock :: MonadResource m => RWLock -> m a -> m a+withReadLock lock action = do+   key <- fst <$> allocate acquireLock releaseLock+   result <- action+   release key+   pure result+   where+   acquireLock = acquireRead lock+   releaseLock = const $ releaseRead lock++withWriteLock :: MonadResource m => RWLock -> m a -> m a+withWriteLock lock action = do+   key <- fst <$> allocate acquireLock releaseLock+   result <- action+   release key+   pure result+   where+   acquireLock = acquireWrite lock+   releaseLock = const $ releaseWrite lock
test/Main.hs view
@@ -84,7 +84,7 @@          printNonMerkleizedRadixTree tree'          pure tree'       Lookup key value -> do-         result <- lookupNonMerkleizedRadixTree key tree+         result <- lookupRadixTree key tree          case result of             Just (value', tree') | value == value' -> pure tree'             Just (value', _) -> throw
test/tests.json view
@@ -5,7 +5,7 @@       "value": "0x4cf812be9be2c6008325050f43d06676a08612c7"     }   ],-  "setAndGet": [+  "insert_and_lookup": [     {       "op": "set",       "key": "0x74657374",@@ -62,6 +62,58 @@       "op": "get",       "key": "0x74657374",       "value": "0x636174313131"+    }+  ],+  "insert_and_lookup_2": [+    {+      "op": "set",+      "key": "0x61",+      "value": "0x3239"+    },+    {+      "op": "set",+      "key": "0x612f",+      "value": "0x3136313438"+    },+    {+      "op": "set",+      "key": "0x612f30",+      "value": "0x36373132"+    },+    {+      "op": "set",+      "key": "0x612f31",+      "value": "0x3732"+    },+    {+      "op": "set",+      "key": "0x62",+      "value": "0x3237"+    },+    {+      "op": "set",+      "key": "0x622f",+      "value": "0x3136313438"+    },+    {+      "op": "set",+      "key": "0x622f30",+      "value": "0x36373132"+    },+    {+      "op": "set",+      "key": "0x622f31",+      "value": "0x3232"+    },+    {+      "op": "set",+      "key": "0x622f31",+      "value": "0x3730"+    },+    {+      "op": "get",+      "key": "0x612f31",+      "value": "0x3732"     }   ],   "insert_and_delete": [