{-# LANGUAGE BangPatterns #-} module Network.Wai.Handler.Warp.MultiMap ( MultiMap , isEmpty , empty , singleton , insert , Network.Wai.Handler.Warp.MultiMap.lookup , pruneWith , toList , merge ) where import Control.Monad (filterM) import Data.Hashable (hash) import Data.IntMap.Strict (IntMap) import qualified Data.IntMap.Strict as I import Data.Semigroup import Prelude -- Silence redundant import warnings ---------------------------------------------------------------- -- | 'MultiMap' is used as a cache of file descriptors. -- Since multiple threads could open file descriptors for -- the same file simultaneously, there could be multiple entries -- for one file. -- Since hash values of file paths are used as outer keys, -- collison would happen for multiple file paths. -- Because only positive entries are stored, -- Malicious attack cannot cause the inner list to blow up. -- So, lists are good enough. newtype MultiMap v = MultiMap (IntMap [(FilePath,v)]) ---------------------------------------------------------------- -- | O(1) empty :: MultiMap v empty = MultiMap I.empty -- | O(1) isEmpty :: MultiMap v -> Bool isEmpty (MultiMap mm) = I.null mm ---------------------------------------------------------------- -- | O(1) singleton :: FilePath -> v -> MultiMap v singleton path v = MultiMap $ I.singleton (hash path) [(path,v)] ---------------------------------------------------------------- -- | O(M) where M is the number of entries per file lookup :: FilePath -> MultiMap v -> Maybe v lookup path (MultiMap mm) = case I.lookup (hash path) mm of Nothing -> Nothing Just s -> Prelude.lookup path s ---------------------------------------------------------------- -- | O(log n) insert :: FilePath -> v -> MultiMap v -> MultiMap v insert path v (MultiMap mm) = MultiMap $ I.insertWith (<>) (hash path) [(path,v)] mm ---------------------------------------------------------------- -- | O(n) toList :: MultiMap v -> [(FilePath,v)] toList (MultiMap mm) = concatMap snd $ I.toAscList mm ---------------------------------------------------------------- -- | O(n) pruneWith :: MultiMap v -> ((FilePath,v) -> IO Bool) -> IO (MultiMap v) pruneWith (MultiMap mm) action = I.foldrWithKey go (pure . MultiMap) mm I.empty where go h s cont acc = do rs <- filterM action s case rs of [] -> cont acc _ -> cont $! I.insert h rs acc ---------------------------------------------------------------- -- O(n + m) where N is the size of the second argument merge :: MultiMap v -> MultiMap v -> MultiMap v merge (MultiMap m1) (MultiMap m2) = MultiMap $ I.unionWith (<>) m1 m2