module Ecluse.Core.Server.Cache (
CacheConfig (..),
defaultCacheConfig,
MetadataCache,
newMetadataCache,
Source (..),
CacheEntry (..),
weighCacheEntry,
resolveMetadata,
resolveMetadataWith,
cachedMetadata,
cacheSize,
resolveVersion,
resolveVersionWith,
cachedVersion,
resolveAssembled,
) where
import Data.Aeson (Value, encode)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL
import Data.Cache (Cache)
import Data.Cache qualified as Cache
import Data.Map.Strict qualified as Map
import Data.Text.Short qualified as TS
import Data.Time (NominalDiffTime)
import System.Clock (Clock (Monotonic), TimeSpec (TimeSpec), getTime)
import UnliftIO.Exception (SomeAsyncException, mask, throwIO)
import Ecluse.Core.InFlight (guardInFlight)
import Ecluse.Core.Package (
PackageDetails,
PackageInfo,
PackageName,
pkgCanonical,
pkgEcosystem,
pkgNamespace,
renderScope,
)
import Ecluse.Core.Registry.Metadata (ContentDigest)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
import Ecluse.Core.Telemetry.Record (
MetricsPort,
mpAssembledCacheResidentBytes,
mpCacheEntries,
mpCacheRequest,
mpCacheResidentBytes,
mpVersionCacheResidentBytes,
)
import Ecluse.Core.Version (Version, renderVersion)
data CacheConfig = CacheConfig
{ CacheConfig -> NominalDiffTime
cacheTtl :: NominalDiffTime
, CacheConfig -> Int
cacheMaxEntries :: Int
, CacheConfig -> Int
cacheMaxBytes :: Int
}
deriving stock (CacheConfig -> CacheConfig -> Bool
(CacheConfig -> CacheConfig -> Bool)
-> (CacheConfig -> CacheConfig -> Bool) -> Eq CacheConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CacheConfig -> CacheConfig -> Bool
== :: CacheConfig -> CacheConfig -> Bool
$c/= :: CacheConfig -> CacheConfig -> Bool
/= :: CacheConfig -> CacheConfig -> Bool
Eq, Int -> CacheConfig -> ShowS
[CacheConfig] -> ShowS
CacheConfig -> String
(Int -> CacheConfig -> ShowS)
-> (CacheConfig -> String)
-> ([CacheConfig] -> ShowS)
-> Show CacheConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CacheConfig -> ShowS
showsPrec :: Int -> CacheConfig -> ShowS
$cshow :: CacheConfig -> String
show :: CacheConfig -> String
$cshowList :: [CacheConfig] -> ShowS
showList :: [CacheConfig] -> ShowS
Show)
defaultCacheConfig :: CacheConfig
defaultCacheConfig :: CacheConfig
defaultCacheConfig =
CacheConfig
{ cacheTtl :: NominalDiffTime
cacheTtl = NominalDiffTime
60
, cacheMaxEntries :: Int
cacheMaxEntries = Int
1024
, cacheMaxBytes :: Int
cacheMaxBytes = Int
256 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024
}
newtype Source = Source Text
deriving stock (Source -> Source -> Bool
(Source -> Source -> Bool)
-> (Source -> Source -> Bool) -> Eq Source
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Source -> Source -> Bool
== :: Source -> Source -> Bool
$c/= :: Source -> Source -> Bool
/= :: Source -> Source -> Bool
Eq, Eq Source
Eq Source =>
(Source -> Source -> Ordering)
-> (Source -> Source -> Bool)
-> (Source -> Source -> Bool)
-> (Source -> Source -> Bool)
-> (Source -> Source -> Bool)
-> (Source -> Source -> Source)
-> (Source -> Source -> Source)
-> Ord Source
Source -> Source -> Bool
Source -> Source -> Ordering
Source -> Source -> Source
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Source -> Source -> Ordering
compare :: Source -> Source -> Ordering
$c< :: Source -> Source -> Bool
< :: Source -> Source -> Bool
$c<= :: Source -> Source -> Bool
<= :: Source -> Source -> Bool
$c> :: Source -> Source -> Bool
> :: Source -> Source -> Bool
$c>= :: Source -> Source -> Bool
>= :: Source -> Source -> Bool
$cmax :: Source -> Source -> Source
max :: Source -> Source -> Source
$cmin :: Source -> Source -> Source
min :: Source -> Source -> Source
Ord, Int -> Source -> ShowS
[Source] -> ShowS
Source -> String
(Int -> Source -> ShowS)
-> (Source -> String) -> ([Source] -> ShowS) -> Show Source
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Source -> ShowS
showsPrec :: Int -> Source -> ShowS
$cshow :: Source -> String
show :: Source -> String
$cshowList :: [Source] -> ShowS
showList :: [Source] -> ShowS
Show)
data CacheEntry = CacheEntry
{ CacheEntry -> PackageInfo
entryInfo :: PackageInfo
, CacheEntry -> Value
entryRaw :: Value
, CacheEntry -> ContentDigest
entryDigest :: ContentDigest
}
deriving stock (CacheEntry -> CacheEntry -> Bool
(CacheEntry -> CacheEntry -> Bool)
-> (CacheEntry -> CacheEntry -> Bool) -> Eq CacheEntry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CacheEntry -> CacheEntry -> Bool
== :: CacheEntry -> CacheEntry -> Bool
$c/= :: CacheEntry -> CacheEntry -> Bool
/= :: CacheEntry -> CacheEntry -> Bool
Eq, Int -> CacheEntry -> ShowS
[CacheEntry] -> ShowS
CacheEntry -> String
(Int -> CacheEntry -> ShowS)
-> (CacheEntry -> String)
-> ([CacheEntry] -> ShowS)
-> Show CacheEntry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CacheEntry -> ShowS
showsPrec :: Int -> CacheEntry -> ShowS
$cshow :: CacheEntry -> String
show :: CacheEntry -> String
$cshowList :: [CacheEntry] -> ShowS
showList :: [CacheEntry] -> ShowS
Show)
weighCacheEntry :: CacheEntry -> Int
weighCacheEntry :: CacheEntry -> Int
weighCacheEntry CacheEntry
e = Int64 -> Int
weighEncodedBytes (ByteString -> Int64
BSL.length (Value -> ByteString
forall a. ToJSON a => a -> ByteString
encode (CacheEntry -> Value
entryRaw CacheEntry
e)))
weighVersion :: Maybe PackageDetails -> Int
weighVersion :: Maybe PackageDetails -> Int
weighVersion = \case
Just PackageDetails
_ -> Int
versionEntryBytes
Maybe PackageDetails
Nothing -> Int
negativeEntryBytes
weighEncodedBytes :: Int64 -> Int
weighEncodedBytes :: Int64 -> Int
weighEncodedBytes Int64
encodedLen = Int64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int64
encodedLen Int64 -> Int64 -> Int64
forall a. Num a => a -> a -> a
* Int64
residentRatioNumerator Int64 -> Int64 -> Int64
forall a. Integral a => a -> a -> a
`div` Int64
residentRatioDenominator)
residentRatioNumerator :: Int64
residentRatioNumerator :: Int64
residentRatioNumerator = Int64
15
residentRatioDenominator :: Int64
residentRatioDenominator :: Int64
residentRatioDenominator = Int64
2
versionEntryBytes :: Int
versionEntryBytes :: Int
versionEntryBytes = Int
16 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024
negativeEntryBytes :: Int
negativeEntryBytes :: Int
negativeEntryBytes = Int
1024
weighAssembled :: ByteString -> Int
weighAssembled :: ByteString -> Int
weighAssembled ByteString
bytes = ByteString -> Int
BS.length ByteString
bytes Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
assembledEntryOverheadBytes
assembledEntryOverheadBytes :: Int
assembledEntryOverheadBytes :: Int
assembledEntryOverheadBytes = Int
256
newtype CacheKey = CacheKey Text
deriving stock (CacheKey -> CacheKey -> Bool
(CacheKey -> CacheKey -> Bool)
-> (CacheKey -> CacheKey -> Bool) -> Eq CacheKey
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CacheKey -> CacheKey -> Bool
== :: CacheKey -> CacheKey -> Bool
$c/= :: CacheKey -> CacheKey -> Bool
/= :: CacheKey -> CacheKey -> Bool
Eq, Eq CacheKey
Eq CacheKey =>
(CacheKey -> CacheKey -> Ordering)
-> (CacheKey -> CacheKey -> Bool)
-> (CacheKey -> CacheKey -> Bool)
-> (CacheKey -> CacheKey -> Bool)
-> (CacheKey -> CacheKey -> Bool)
-> (CacheKey -> CacheKey -> CacheKey)
-> (CacheKey -> CacheKey -> CacheKey)
-> Ord CacheKey
CacheKey -> CacheKey -> Bool
CacheKey -> CacheKey -> Ordering
CacheKey -> CacheKey -> CacheKey
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: CacheKey -> CacheKey -> Ordering
compare :: CacheKey -> CacheKey -> Ordering
$c< :: CacheKey -> CacheKey -> Bool
< :: CacheKey -> CacheKey -> Bool
$c<= :: CacheKey -> CacheKey -> Bool
<= :: CacheKey -> CacheKey -> Bool
$c> :: CacheKey -> CacheKey -> Bool
> :: CacheKey -> CacheKey -> Bool
$c>= :: CacheKey -> CacheKey -> Bool
>= :: CacheKey -> CacheKey -> Bool
$cmax :: CacheKey -> CacheKey -> CacheKey
max :: CacheKey -> CacheKey -> CacheKey
$cmin :: CacheKey -> CacheKey -> CacheKey
min :: CacheKey -> CacheKey -> CacheKey
Ord, Int -> CacheKey -> ShowS
[CacheKey] -> ShowS
CacheKey -> String
(Int -> CacheKey -> ShowS)
-> (CacheKey -> String) -> ([CacheKey] -> ShowS) -> Show CacheKey
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CacheKey -> ShowS
showsPrec :: Int -> CacheKey -> ShowS
$cshow :: CacheKey -> String
show :: CacheKey -> String
$cshowList :: [CacheKey] -> ShowS
showList :: [CacheKey] -> ShowS
Show)
deriving newtype (Eq CacheKey
Eq CacheKey =>
(Int -> CacheKey -> Int) -> (CacheKey -> Int) -> Hashable CacheKey
Int -> CacheKey -> Int
CacheKey -> Int
forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
$chashWithSalt :: Int -> CacheKey -> Int
hashWithSalt :: Int -> CacheKey -> Int
$chash :: CacheKey -> Int
hash :: CacheKey -> Int
Hashable)
keyText :: Source -> PackageName -> Text
keyText :: Source -> PackageName -> Text
keyText (Source Text
source) PackageName
name =
Text
source
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\x1f"
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text
forall b a. (Show a, IsString b) => a -> b
show (PackageName -> Ecosystem
pkgEcosystem PackageName
name)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\x1f"
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> (Scope -> Text) -> Maybe Scope -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" Scope -> Text
renderScope (PackageName -> Maybe Scope
pkgNamespace PackageName
name)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\x1f"
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ShortText -> Text
TS.toText (PackageName -> ShortText
pkgCanonical PackageName
name)
cacheKey :: Source -> PackageName -> CacheKey
cacheKey :: Source -> PackageName -> CacheKey
cacheKey Source
source PackageName
name = Text -> CacheKey
CacheKey (Source -> PackageName -> Text
keyText Source
source PackageName
name)
newtype VersionKey = VersionKey Text
deriving stock (VersionKey -> VersionKey -> Bool
(VersionKey -> VersionKey -> Bool)
-> (VersionKey -> VersionKey -> Bool) -> Eq VersionKey
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: VersionKey -> VersionKey -> Bool
== :: VersionKey -> VersionKey -> Bool
$c/= :: VersionKey -> VersionKey -> Bool
/= :: VersionKey -> VersionKey -> Bool
Eq, Eq VersionKey
Eq VersionKey =>
(VersionKey -> VersionKey -> Ordering)
-> (VersionKey -> VersionKey -> Bool)
-> (VersionKey -> VersionKey -> Bool)
-> (VersionKey -> VersionKey -> Bool)
-> (VersionKey -> VersionKey -> Bool)
-> (VersionKey -> VersionKey -> VersionKey)
-> (VersionKey -> VersionKey -> VersionKey)
-> Ord VersionKey
VersionKey -> VersionKey -> Bool
VersionKey -> VersionKey -> Ordering
VersionKey -> VersionKey -> VersionKey
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: VersionKey -> VersionKey -> Ordering
compare :: VersionKey -> VersionKey -> Ordering
$c< :: VersionKey -> VersionKey -> Bool
< :: VersionKey -> VersionKey -> Bool
$c<= :: VersionKey -> VersionKey -> Bool
<= :: VersionKey -> VersionKey -> Bool
$c> :: VersionKey -> VersionKey -> Bool
> :: VersionKey -> VersionKey -> Bool
$c>= :: VersionKey -> VersionKey -> Bool
>= :: VersionKey -> VersionKey -> Bool
$cmax :: VersionKey -> VersionKey -> VersionKey
max :: VersionKey -> VersionKey -> VersionKey
$cmin :: VersionKey -> VersionKey -> VersionKey
min :: VersionKey -> VersionKey -> VersionKey
Ord, Int -> VersionKey -> ShowS
[VersionKey] -> ShowS
VersionKey -> String
(Int -> VersionKey -> ShowS)
-> (VersionKey -> String)
-> ([VersionKey] -> ShowS)
-> Show VersionKey
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> VersionKey -> ShowS
showsPrec :: Int -> VersionKey -> ShowS
$cshow :: VersionKey -> String
show :: VersionKey -> String
$cshowList :: [VersionKey] -> ShowS
showList :: [VersionKey] -> ShowS
Show)
deriving newtype (Eq VersionKey
Eq VersionKey =>
(Int -> VersionKey -> Int)
-> (VersionKey -> Int) -> Hashable VersionKey
Int -> VersionKey -> Int
VersionKey -> Int
forall a. Eq a => (Int -> a -> Int) -> (a -> Int) -> Hashable a
$chashWithSalt :: Int -> VersionKey -> Int
hashWithSalt :: Int -> VersionKey -> Int
$chash :: VersionKey -> Int
hash :: VersionKey -> Int
Hashable)
versionKey :: Source -> PackageName -> Version -> VersionKey
versionKey :: Source -> PackageName -> Version -> VersionKey
versionKey Source
source PackageName
name Version
version = Text -> VersionKey
VersionKey (Source -> PackageName -> Text
keyText Source
source PackageName
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\x1f" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Version -> Text
renderVersion Version
version)
data Weighted v = Weighted
{ forall v. Weighted v -> v
wValue :: v
, forall v. Weighted v -> Int
wWeight :: Int
, forall v. Weighted v -> IORef Word64
wStamp :: IORef Word64
}
data SingleFlight k v = SingleFlight
{ forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore :: Cache k (Weighted v)
, forall k v. SingleFlight k v -> Int
sfMaxEntries :: Int
, forall k v. SingleFlight k v -> Int
sfMaxBytes :: Int
, forall k v. SingleFlight k v -> v -> Int
sfWeigh :: v -> Int
, forall k v. SingleFlight k v -> IORef Word64
sfClock :: IORef Word64
, forall k v.
SingleFlight k v -> TVar (Map k (TMVar (Either SomeException v)))
sfInFlight :: TVar (Map k (TMVar (Either SomeException v)))
}
newSingleFlight :: CacheConfig -> (v -> Int) -> IO (SingleFlight k v)
newSingleFlight :: forall v k. CacheConfig -> (v -> Int) -> IO (SingleFlight k v)
newSingleFlight CacheConfig
cfg v -> Int
weigh = do
store <- Maybe TimeSpec -> IO (Cache k (Weighted v))
forall k v. Maybe TimeSpec -> IO (Cache k v)
Cache.newCache (TimeSpec -> Maybe TimeSpec
forall a. a -> Maybe a
Just (NominalDiffTime -> TimeSpec
toTimeSpec (CacheConfig -> NominalDiffTime
cacheTtl CacheConfig
cfg)))
clock <- newIORef 0
inFlight <- newTVarIO Map.empty
pure
SingleFlight
{ sfStore = store
, sfMaxEntries = max 1 (cacheMaxEntries cfg)
, sfMaxBytes = max 1 (cacheMaxBytes cfg)
, sfWeigh = weigh
, sfClock = clock
, sfInFlight = inFlight
}
data MetadataCache = MetadataCache
{ MetadataCache -> SingleFlight CacheKey CacheEntry
mcFull :: SingleFlight CacheKey CacheEntry
, MetadataCache -> SingleFlight VersionKey (Maybe PackageDetails)
mcVersion :: SingleFlight VersionKey (Maybe PackageDetails)
, MetadataCache -> SingleFlight Text ByteString
mcAssembled :: SingleFlight Text ByteString
}
newMetadataCache :: CacheConfig -> IO MetadataCache
newMetadataCache :: CacheConfig -> IO MetadataCache
newMetadataCache CacheConfig
cfg =
SingleFlight CacheKey CacheEntry
-> SingleFlight VersionKey (Maybe PackageDetails)
-> SingleFlight Text ByteString
-> MetadataCache
MetadataCache
(SingleFlight CacheKey CacheEntry
-> SingleFlight VersionKey (Maybe PackageDetails)
-> SingleFlight Text ByteString
-> MetadataCache)
-> IO (SingleFlight CacheKey CacheEntry)
-> IO
(SingleFlight VersionKey (Maybe PackageDetails)
-> SingleFlight Text ByteString -> MetadataCache)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> CacheConfig
-> (CacheEntry -> Int) -> IO (SingleFlight CacheKey CacheEntry)
forall v k. CacheConfig -> (v -> Int) -> IO (SingleFlight k v)
newSingleFlight CacheConfig
cfg CacheEntry -> Int
weighCacheEntry
IO
(SingleFlight VersionKey (Maybe PackageDetails)
-> SingleFlight Text ByteString -> MetadataCache)
-> IO (SingleFlight VersionKey (Maybe PackageDetails))
-> IO (SingleFlight Text ByteString -> MetadataCache)
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> CacheConfig
-> (Maybe PackageDetails -> Int)
-> IO (SingleFlight VersionKey (Maybe PackageDetails))
forall v k. CacheConfig -> (v -> Int) -> IO (SingleFlight k v)
newSingleFlight CacheConfig
cfg Maybe PackageDetails -> Int
weighVersion
IO (SingleFlight Text ByteString -> MetadataCache)
-> IO (SingleFlight Text ByteString) -> IO MetadataCache
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> CacheConfig
-> (ByteString -> Int) -> IO (SingleFlight Text ByteString)
forall v k. CacheConfig -> (v -> Int) -> IO (SingleFlight k v)
newSingleFlight CacheConfig
cfg ByteString -> Int
weighAssembled
resolveMetadata :: MetricsPort -> MetadataCache -> Source -> PackageName -> IO CacheEntry -> IO CacheEntry
resolveMetadata :: MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> IO CacheEntry
-> IO CacheEntry
resolveMetadata = IO ()
-> MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> IO CacheEntry
-> IO CacheEntry
resolveMetadataWith (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())
resolveMetadataWith :: IO () -> MetricsPort -> MetadataCache -> Source -> PackageName -> IO CacheEntry -> IO CacheEntry
resolveMetadataWith :: IO ()
-> MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> IO CacheEntry
-> IO CacheEntry
resolveMetadataWith IO ()
afterClaim MetricsPort
metrics MetadataCache
cache Source
source PackageName
name =
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight CacheKey CacheEntry
-> CacheKey
-> IO CacheEntry
-> IO CacheEntry
forall k v.
(Hashable k, Ord k) =>
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight k v
-> k
-> IO v
-> IO v
resolveSingleFlight
IO ()
afterClaim
(MetricsPort -> CacheResult -> IO ()
mpCacheRequest MetricsPort
metrics)
( \CacheOccupancy
occ -> do
MetricsPort -> Int -> IO ()
mpCacheEntries MetricsPort
metrics (CacheOccupancy -> Int
occEntries CacheOccupancy
occ)
MetricsPort -> Int -> IO ()
mpCacheResidentBytes MetricsPort
metrics (CacheOccupancy -> Int
occBytes CacheOccupancy
occ)
)
(MetadataCache -> SingleFlight CacheKey CacheEntry
mcFull MetadataCache
cache)
(Source -> PackageName -> CacheKey
cacheKey Source
source PackageName
name)
resolveVersion :: MetricsPort -> MetadataCache -> Source -> PackageName -> Version -> IO (Maybe PackageDetails) -> IO (Maybe PackageDetails)
resolveVersion :: MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> Version
-> IO (Maybe PackageDetails)
-> IO (Maybe PackageDetails)
resolveVersion = IO ()
-> MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> Version
-> IO (Maybe PackageDetails)
-> IO (Maybe PackageDetails)
resolveVersionWith (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())
resolveVersionWith :: IO () -> MetricsPort -> MetadataCache -> Source -> PackageName -> Version -> IO (Maybe PackageDetails) -> IO (Maybe PackageDetails)
resolveVersionWith :: IO ()
-> MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> Version
-> IO (Maybe PackageDetails)
-> IO (Maybe PackageDetails)
resolveVersionWith IO ()
afterClaim MetricsPort
metrics MetadataCache
cache Source
source PackageName
name Version
version =
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight VersionKey (Maybe PackageDetails)
-> VersionKey
-> IO (Maybe PackageDetails)
-> IO (Maybe PackageDetails)
forall k v.
(Hashable k, Ord k) =>
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight k v
-> k
-> IO v
-> IO v
resolveSingleFlight
IO ()
afterClaim
(IO () -> CacheResult -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass)
(MetricsPort -> Int -> IO ()
mpVersionCacheResidentBytes MetricsPort
metrics (Int -> IO ())
-> (CacheOccupancy -> Int) -> CacheOccupancy -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CacheOccupancy -> Int
occBytes)
(MetadataCache -> SingleFlight VersionKey (Maybe PackageDetails)
mcVersion MetadataCache
cache)
(Source -> PackageName -> Version -> VersionKey
versionKey Source
source PackageName
name Version
version)
resolveAssembled :: MetricsPort -> MetadataCache -> Text -> IO ByteString -> IO ByteString
resolveAssembled :: MetricsPort
-> MetadataCache -> Text -> IO ByteString -> IO ByteString
resolveAssembled MetricsPort
metrics MetadataCache
cache =
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight Text ByteString
-> Text
-> IO ByteString
-> IO ByteString
forall k v.
(Hashable k, Ord k) =>
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight k v
-> k
-> IO v
-> IO v
resolveSingleFlight
(() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())
(IO () -> CacheResult -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass)
(MetricsPort -> Int -> IO ()
mpAssembledCacheResidentBytes MetricsPort
metrics (Int -> IO ())
-> (CacheOccupancy -> Int) -> CacheOccupancy -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CacheOccupancy -> Int
occBytes)
(MetadataCache -> SingleFlight Text ByteString
mcAssembled MetadataCache
cache)
resolveSingleFlight ::
(Hashable k, Ord k) =>
IO () ->
(Metric.CacheResult -> IO ()) ->
(CacheOccupancy -> IO ()) ->
SingleFlight k v ->
k ->
IO v ->
IO v
resolveSingleFlight :: forall k v.
(Hashable k, Ord k) =>
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight k v
-> k
-> IO v
-> IO v
resolveSingleFlight IO ()
afterClaim CacheResult -> IO ()
recordRequest CacheOccupancy -> IO ()
recordInsert SingleFlight k v
sf k
key IO v
fetch = ((forall a. IO a -> IO a) -> IO v) -> IO v
forall (m :: * -> *) b.
MonadUnliftIO m =>
((forall a. m a -> m a) -> m b) -> m b
mask (((forall a. IO a -> IO a) -> IO v) -> IO v)
-> ((forall a. IO a -> IO a) -> IO v) -> IO v
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore -> do
nowT <- Clock -> IO TimeSpec
getTime Clock
Monotonic
decision <- atomically (decideSingleFlight sf key nowT)
case decision of
Hit Weighted v
weighted -> do
CacheResult -> IO ()
recordRequest CacheResult
Metric.Hit
SingleFlight k v -> Weighted v -> IO ()
forall k v. SingleFlight k v -> Weighted v -> IO ()
touch SingleFlight k v
sf Weighted v
weighted
v -> IO v
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Weighted v -> v
forall v. Weighted v -> v
wValue Weighted v
weighted)
Follow TMVar (Either SomeException v)
marker -> do
CacheResult -> IO ()
recordRequest CacheResult
Metric.Miss
result <- IO (Either SomeException v) -> IO (Either SomeException v)
forall a. IO a -> IO a
restore (STM (Either SomeException v) -> IO (Either SomeException v)
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (TMVar (Either SomeException v) -> STM (Either SomeException v)
forall a. TMVar a -> STM a
readTMVar TMVar (Either SomeException v)
marker))
case result of
Right v
fetched -> v -> IO v
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure v
fetched
Left SomeException
err -> case SomeException -> Maybe SomeAsyncException
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
err of
Just (SomeAsyncException
_ :: SomeAsyncException) ->
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight k v
-> k
-> IO v
-> IO v
forall k v.
(Hashable k, Ord k) =>
IO ()
-> (CacheResult -> IO ())
-> (CacheOccupancy -> IO ())
-> SingleFlight k v
-> k
-> IO v
-> IO v
resolveSingleFlight IO ()
afterClaim CacheResult -> IO ()
recordRequest CacheOccupancy -> IO ()
recordInsert SingleFlight k v
sf k
key IO v
fetch
Maybe SomeAsyncException
Nothing -> SomeException -> IO v
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO SomeException
err
Lead TMVar (Either SomeException v)
marker -> do
CacheResult -> IO ()
recordRequest CacheResult
Metric.Miss
(entry, occupancy) <- (IO (v, CacheOccupancy) -> IO (v, CacheOccupancy))
-> (SomeException -> IO ())
-> IO ()
-> IO (v, CacheOccupancy)
-> IO (v, CacheOccupancy)
forall a.
(IO a -> IO a) -> (SomeException -> IO ()) -> IO () -> IO a -> IO a
guardInFlight IO (v, CacheOccupancy) -> IO (v, CacheOccupancy)
forall a. a -> a
id (TMVar (Either SomeException v) -> SomeException -> IO ()
forall v. TMVar (Either SomeException v) -> SomeException -> IO ()
orphan TMVar (Either SomeException v)
marker) (STM () -> IO ()
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically STM ()
deregister) (IO (v, CacheOccupancy) -> IO (v, CacheOccupancy))
-> IO (v, CacheOccupancy) -> IO (v, CacheOccupancy)
forall a b. (a -> b) -> a -> b
$ do
fetched <- IO v -> IO v
forall a. IO a -> IO a
restore (IO ()
afterClaim IO () -> IO v -> IO v
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO v
fetch)
atomically (putTMVar marker (Right fetched))
occupancy <- insertBounded sf key fetched
pure (fetched, occupancy)
recordInsert occupancy
pure entry
where
deregister :: STM ()
deregister :: STM ()
deregister = do
inFlight <- TVar (Map k (TMVar (Either SomeException v)))
-> STM (Map k (TMVar (Either SomeException v)))
forall a. TVar a -> STM a
readTVar (SingleFlight k v -> TVar (Map k (TMVar (Either SomeException v)))
forall k v.
SingleFlight k v -> TVar (Map k (TMVar (Either SomeException v)))
sfInFlight SingleFlight k v
sf)
writeTVar (sfInFlight sf) (Map.delete key inFlight)
insertBounded :: (Hashable k) => SingleFlight k v -> k -> v -> IO CacheOccupancy
insertBounded :: forall k v.
Hashable k =>
SingleFlight k v -> k -> v -> IO CacheOccupancy
insertBounded SingleFlight k v
sf k
key v
value = do
Cache k (Weighted v) -> IO ()
forall k v. (Eq k, Hashable k) => Cache k v -> IO ()
Cache.purgeExpired (SingleFlight k v -> Cache k (Weighted v)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore SingleFlight k v
sf)
let weight :: Int
weight = SingleFlight k v -> v -> Int
forall k v. SingleFlight k v -> v -> Int
sfWeigh SingleFlight k v
sf v
value
SingleFlight k v -> Int -> IO ()
forall k v. Hashable k => SingleFlight k v -> Int -> IO ()
evictToBudget SingleFlight k v
sf Int
weight
stamp <- SingleFlight k v -> IO Word64
forall k v. SingleFlight k v -> IO Word64
nextStamp SingleFlight k v
sf
stampRef <- newIORef stamp
Cache.insert (sfStore sf) key (Weighted{wValue = value, wWeight = weight, wStamp = stampRef})
occupancyOf sf
evictToBudget :: (Hashable k) => SingleFlight k v -> Int -> IO ()
evictToBudget :: forall k v. Hashable k => SingleFlight k v -> Int -> IO ()
evictToBudget SingleFlight k v
sf Int
incoming = do
held <- Cache k (Weighted v) -> IO [(k, Weighted v, Maybe TimeSpec)]
forall k v. Cache k v -> IO [(k, v, Maybe TimeSpec)]
Cache.toList (SingleFlight k v -> Cache k (Weighted v)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore SingleFlight k v
sf)
stamped <- traverse stampOf held
let resident = [Int] -> Int
forall a (f :: * -> *). (Foldable f, Num a) => f a -> a
sum [Weighted v -> Int
forall v. Weighted v -> Int
wWeight Weighted v
w | (k
_, Weighted v
w, Maybe TimeSpec
_) <- [(k, Weighted v, Maybe TimeSpec)]
held]
oldestFirst = ((Word64, k, Int) -> Word64)
-> [(Word64, k, Int)] -> [(Word64, k, Int)]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (\(Word64
stamp, k
_, Int
_) -> Word64
stamp) [(Word64, k, Int)]
stamped
go oldestFirst resident (length held)
where
stampOf :: (b, Weighted v, c) -> m (Word64, b, Int)
stampOf (b
k, Weighted v
w, c
_) = do
s <- IORef Word64 -> m Word64
forall (m :: * -> *) a. MonadIO m => IORef a -> m a
readIORef (Weighted v -> IORef Word64
forall v. Weighted v -> IORef Word64
wStamp Weighted v
w)
pure (s, k, wWeight w)
fits :: Int -> Int -> Bool
fits Int
resident Int
count = Int
resident Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
incoming Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= SingleFlight k v -> Int
forall k v. SingleFlight k v -> Int
sfMaxBytes SingleFlight k v
sf Bool -> Bool -> Bool
&& Int
count Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< SingleFlight k v -> Int
forall k v. SingleFlight k v -> Int
sfMaxEntries SingleFlight k v
sf
go :: [(a, k, Int)] -> Int -> Int -> IO ()
go [(a, k, Int)]
victims Int
resident Int
count
| Int -> Int -> Bool
fits Int
resident Int
count = IO ()
forall (f :: * -> *). Applicative f => f ()
pass
| Bool
otherwise = case [(a, k, Int)]
victims of
[] -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
((a
_, k
k, Int
weight) : [(a, k, Int)]
rest) -> do
Cache k (Weighted v) -> k -> IO ()
forall k v. (Eq k, Hashable k) => Cache k v -> k -> IO ()
Cache.delete (SingleFlight k v -> Cache k (Weighted v)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore SingleFlight k v
sf) k
k
[(a, k, Int)] -> Int -> Int -> IO ()
go [(a, k, Int)]
rest (Int
resident Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
weight) (Int
count Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1)
occupancyOf :: SingleFlight k v -> IO CacheOccupancy
occupancyOf :: forall k v. SingleFlight k v -> IO CacheOccupancy
occupancyOf SingleFlight k v
sf = do
held <- Cache k (Weighted v) -> IO [(k, Weighted v, Maybe TimeSpec)]
forall k v. Cache k v -> IO [(k, v, Maybe TimeSpec)]
Cache.toList (SingleFlight k v -> Cache k (Weighted v)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore SingleFlight k v
sf)
pure CacheOccupancy{occEntries = length held, occBytes = sum [wWeight w | (_, w, _) <- held]}
nextStamp :: SingleFlight k v -> IO Word64
nextStamp :: forall k v. SingleFlight k v -> IO Word64
nextStamp SingleFlight k v
sf = IORef Word64 -> (Word64 -> (Word64, Word64)) -> IO Word64
forall (m :: * -> *) a b.
MonadIO m =>
IORef a -> (a -> (a, b)) -> m b
atomicModifyIORef' (SingleFlight k v -> IORef Word64
forall k v. SingleFlight k v -> IORef Word64
sfClock SingleFlight k v
sf) (\Word64
n -> let n' :: Word64
n' = Word64
n Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word64
1 in (Word64
n', Word64
n'))
touch :: SingleFlight k v -> Weighted v -> IO ()
touch :: forall k v. SingleFlight k v -> Weighted v -> IO ()
touch SingleFlight k v
sf Weighted v
weighted = SingleFlight k v -> IO Word64
forall k v. SingleFlight k v -> IO Word64
nextStamp SingleFlight k v
sf IO Word64 -> (Word64 -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IORef Word64 -> Word64 -> IO ()
forall (m :: * -> *) a. MonadIO m => IORef a -> a -> m ()
writeIORef (Weighted v -> IORef Word64
forall v. Weighted v -> IORef Word64
wStamp Weighted v
weighted)
cachedMetadata :: MetadataCache -> Source -> PackageName -> IO (Maybe CacheEntry)
cachedMetadata :: MetadataCache -> Source -> PackageName -> IO (Maybe CacheEntry)
cachedMetadata MetadataCache
cache Source
source PackageName
name = (Weighted CacheEntry -> CacheEntry)
-> Maybe (Weighted CacheEntry) -> Maybe CacheEntry
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Weighted CacheEntry -> CacheEntry
forall v. Weighted v -> v
wValue (Maybe (Weighted CacheEntry) -> Maybe CacheEntry)
-> IO (Maybe (Weighted CacheEntry)) -> IO (Maybe CacheEntry)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Cache CacheKey (Weighted CacheEntry)
-> CacheKey -> IO (Maybe (Weighted CacheEntry))
forall k v. (Eq k, Hashable k) => Cache k v -> k -> IO (Maybe v)
Cache.lookup (SingleFlight CacheKey CacheEntry
-> Cache CacheKey (Weighted CacheEntry)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore (MetadataCache -> SingleFlight CacheKey CacheEntry
mcFull MetadataCache
cache)) (Source -> PackageName -> CacheKey
cacheKey Source
source PackageName
name)
cachedVersion :: MetadataCache -> Source -> PackageName -> Version -> IO (Maybe (Maybe PackageDetails))
cachedVersion :: MetadataCache
-> Source
-> PackageName
-> Version
-> IO (Maybe (Maybe PackageDetails))
cachedVersion MetadataCache
cache Source
source PackageName
name Version
version = (Weighted (Maybe PackageDetails) -> Maybe PackageDetails)
-> Maybe (Weighted (Maybe PackageDetails))
-> Maybe (Maybe PackageDetails)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Weighted (Maybe PackageDetails) -> Maybe PackageDetails
forall v. Weighted v -> v
wValue (Maybe (Weighted (Maybe PackageDetails))
-> Maybe (Maybe PackageDetails))
-> IO (Maybe (Weighted (Maybe PackageDetails)))
-> IO (Maybe (Maybe PackageDetails))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Cache VersionKey (Weighted (Maybe PackageDetails))
-> VersionKey -> IO (Maybe (Weighted (Maybe PackageDetails)))
forall k v. (Eq k, Hashable k) => Cache k v -> k -> IO (Maybe v)
Cache.lookup (SingleFlight VersionKey (Maybe PackageDetails)
-> Cache VersionKey (Weighted (Maybe PackageDetails))
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore (MetadataCache -> SingleFlight VersionKey (Maybe PackageDetails)
mcVersion MetadataCache
cache)) (Source -> PackageName -> Version -> VersionKey
versionKey Source
source PackageName
name Version
version)
cacheSize :: MetadataCache -> IO Int
cacheSize :: MetadataCache -> IO Int
cacheSize MetadataCache
cache = Cache CacheKey (Weighted CacheEntry) -> IO Int
forall k v. Cache k v -> IO Int
Cache.size (SingleFlight CacheKey CacheEntry
-> Cache CacheKey (Weighted CacheEntry)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore (MetadataCache -> SingleFlight CacheKey CacheEntry
mcFull MetadataCache
cache))
data Decision v
= Hit (Weighted v)
| Follow (TMVar (Either SomeException v))
| Lead (TMVar (Either SomeException v))
decideSingleFlight :: (Hashable k, Ord k) => SingleFlight k v -> k -> TimeSpec -> STM (Decision v)
decideSingleFlight :: forall k v.
(Hashable k, Ord k) =>
SingleFlight k v -> k -> TimeSpec -> STM (Decision v)
decideSingleFlight SingleFlight k v
sf k
key TimeSpec
nowT = do
hit <- Bool
-> k
-> Cache k (Weighted v)
-> TimeSpec
-> STM (Maybe (Weighted v))
forall k v.
(Eq k, Hashable k) =>
Bool -> k -> Cache k v -> TimeSpec -> STM (Maybe v)
Cache.lookupSTM Bool
False k
key (SingleFlight k v -> Cache k (Weighted v)
forall k v. SingleFlight k v -> Cache k (Weighted v)
sfStore SingleFlight k v
sf) TimeSpec
nowT
case hit of
Just Weighted v
weighted -> Decision v -> STM (Decision v)
forall a. a -> STM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Weighted v -> Decision v
forall v. Weighted v -> Decision v
Hit Weighted v
weighted)
Maybe (Weighted v)
Nothing -> do
inFlight <- TVar (Map k (TMVar (Either SomeException v)))
-> STM (Map k (TMVar (Either SomeException v)))
forall a. TVar a -> STM a
readTVar (SingleFlight k v -> TVar (Map k (TMVar (Either SomeException v)))
forall k v.
SingleFlight k v -> TVar (Map k (TMVar (Either SomeException v)))
sfInFlight SingleFlight k v
sf)
case Map.lookup key inFlight of
Just TMVar (Either SomeException v)
marker -> Decision v -> STM (Decision v)
forall a. a -> STM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (TMVar (Either SomeException v) -> Decision v
forall v. TMVar (Either SomeException v) -> Decision v
Follow TMVar (Either SomeException v)
marker)
Maybe (TMVar (Either SomeException v))
Nothing -> do
marker <- STM (TMVar (Either SomeException v))
forall a. STM (TMVar a)
newEmptyTMVar
writeTVar (sfInFlight sf) (Map.insert key marker inFlight)
pure (Lead marker)
orphan :: TMVar (Either SomeException v) -> SomeException -> IO ()
orphan :: forall v. TMVar (Either SomeException v) -> SomeException -> IO ()
orphan TMVar (Either SomeException v)
marker SomeException
err =
STM () -> IO ()
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
unfilled <- TMVar (Either SomeException v) -> STM Bool
forall a. TMVar a -> STM Bool
isEmptyTMVar TMVar (Either SomeException v)
marker
when unfilled (putTMVar marker (Left err))
data CacheOccupancy = CacheOccupancy
{ CacheOccupancy -> Int
occEntries :: Int
, CacheOccupancy -> Int
occBytes :: Int
}
toTimeSpec :: NominalDiffTime -> TimeSpec
toTimeSpec :: NominalDiffTime -> TimeSpec
toTimeSpec NominalDiffTime
ttl =
let nanos :: Integer
nanos = Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
max Integer
0 (Double -> Integer
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (NominalDiffTime -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac NominalDiffTime
ttl Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
1e9 :: Double)) :: Integer
billion :: Integer
billion = Integer
1000000000
in Int64 -> Int64 -> TimeSpec
TimeSpec
(Integer -> Int64
forall a. Num a => Integer -> a
fromInteger (Integer
nanos Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
billion))
(Integer -> Int64
forall a. Num a => Integer -> a
fromInteger (Integer
nanos Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`mod` Integer
billion))