Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file environment_cache.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2021 Nomadic Labs, <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)typesize=inttypeindex=inttypeidentifier=stringtypekey={identifier:identifier;cache_index:index}letkey_encoding=Data_encoding.(conv(funkey->(key.identifier,key.cache_index))(fun(identifier,cache_index)->{identifier;cache_index})(tup2stringint16))moduleKey=structtypet=keyletcomparek1k2=String.comparek1.identifierk2.identifierendmoduleKeyMap=Map.Make(Key)moduleKeySet=Set.Make(Key)typevalue_metadata={size:int;birth:int64;cache_nonce:Bytes.t}letvalue_metadata_encoding:value_metadataData_encoding.t=Data_encoding.(conv(funentry->(entry.size,entry.birth,entry.cache_nonce))(fun(size,birth,cache_nonce)->{size;birth;cache_nonce})(tup3int31int64Variable.bytes))letpp_entryppf(entry:value_metadata)=Format.fprintfppf"%d/%Ld/%a"entry.sizeentry.birthHex.pp(Hex.of_bytesentry.cache_nonce)letequal_value_metadatam1m2=m1.size=m2.size&&m1.birth=m2.birth&&Bytes.equalm1.cache_noncem2.cache_noncemoduleInt64Map=Map.Make(Int64)type'acache={(* Each cache has a handle in the context caches. *)index:index;(* [map] collects the cache entries. *)map:('a*value_metadata)KeyMap.t;(* [lru] maintains a fast index from [birth] to entries. In
particular, it provides a logarithmic access to the Least
Recently Used entry. *)lru:keyInt64Map.t;(* [size] is the sum of all entry sizes. *)size:int;(* [limit] is the maximal size of the cache in memory. This [limit]
MUST be greater than any entry size added in cache. This assumption
is used for the correctness of the implementation. We enforce
this property by preventing any too large entry from entering
the cache. Similarly, we enforce the invariant that no entry of null
size can enter the cache. *)limit:int;(* [counter] is the maximal age of entries that have been inserted
in the cache since its creation. Assuming 100_000 new entries per
second, [counter] will not overflow before ~3 million years. *)counter:int64;(* [removed_entries] maintains the keys removed since last
synchronization. *)removed_entries:KeySet.t;(* [entries_removals] maintains the last numbers of entries removal
per block. This list cannot be longer than
[entries_removals_window_width]. *)entries_removals:intlist;}type'at='acacheFunctionalArray.toptionletstring_of_key{identifier;_}=identifierletpp_cachefmt{index;map;size;limit;counter;_}=Format.fprintffmt"@[<v 0>Index: %d@,Cardinal: %d@,Size limit: %d@,Size: %d@,Counter: %Ld%a@]"index(KeyMap.cardinalmap)limitsizecounter(funppfmap->KeyMap.iter(funk(_,entry)->Format.fprintfppf"@,Element %s: %a"(string_of_keyk)pp_entryentry)map)mapletinvalid_arg_with_callstackmsg=letcs=Printexc.get_callstack15inFormat.kasprintfinvalid_arg"Internal error: %s\nCall stack:\n%s\n"msg(Printexc.raw_backtrace_to_stringcs)letwith_cachescachef=matchcachewith|None->invalid_arg_with_callstack"uninitialized caches"|Somecaches->fcachesletcache_of_indextindex=with_cachest(funcaches->FunctionalArray.getcachesindex)letcache_of_keycacheskey=cache_of_indexcacheskey.cache_indexletlookup_entrycachekey=KeyMap.findkeycache.mapletlookup_valuecachekey=matchlookup_entrycachekeywithSome(e,_)->Somee|None->Noneletlookuptkey=lookup_entry(cache_of_keytkey)keyletupdate_cache_withtindexcache=with_cachest(funcaches->Some(FunctionalArray.setcachesindexcache))letempty_cache={index=-1;map=KeyMap.empty;lru=Int64Map.empty;size=0;counter=0L;removed_entries=KeySet.empty;entries_removals=[];limit=-1;}letmake_caches(layout:sizelist)=List.iter(funsize->ifsize<0theninvalid_arg_with_callstack"sizes in layout must be nonnegative")layout;letdefault=FunctionalArray.make(List.lengthlayout)empty_cacheinletfolderindexarraylimit=FunctionalArray.setarrayindex{empty_cachewithlimit;index}inList.fold_left_ifolderdefaultlayout(*
When an entry is fresh, it is assigned a [fresh_entry_nonce].
The actual nonce for this entry will be known only when its block
is finalized: it is only in function [sync] that
[fresh_entry_nonce] is substituted by a valid [nonce].
*)letfresh_entry_nonce=Bytes.of_string"__FRESH_ENTRY_NONCE__"letremove_cache_entrycachekeyentry={cachewithmap=KeyMap.removekeycache.map;size=cache.size-entry.size;lru=Int64Map.removeentry.birthcache.lru;removed_entries=KeySet.addkeycache.removed_entries;}(* The dean is the oldest entry.
The complexity of this operation is logarithmic in the number of
entries in the cache. Along a given chain, [dean cache] only
increases. *)letdeancache:(int64*key)option=Int64Map.min_bindingcache.lruletremove_deancache=matchdeancachewith|None->(* This case is unreachable because [remove_dean] is always called
by [enforce_size_limit] with a nonempty cache. *)cache|Some(_,key)->(matchKeyMap.findkeycache.mapwith|None->assertfalse(* because [lru] must point to keys that are in [map]. *)|Some(_,entry)->remove_cache_entrycachekeyentry)letrecenforce_size_limitcache=ifcache.size>cache.limitthenremove_deancache(* [size] has decreased strictly because if size > limit, then the
cache cannot be empty. Hence, this recursive call will
converge. *)|>enforce_size_limitelsecacheletinsert_cache_entrycachekey((_,{size;birth;_})asentry)={cachewithmap=KeyMap.addkeyentrycache.map;size=cache.size+size;counter=maxcache.counterbirth;lru=Int64Map.addbirthkeycache.lru;removed_entries=KeySet.removekeycache.removed_entries;}|>enforce_size_limitletinsert_cachecachekeyvaluesizecache_nonce=(* Conforming to entry size invariant: we need this size to be
strictly positive. *)letsize=max1sizeinletentry={size;birth=Int64.addcache.counter1L;cache_nonce}ininsert_cache_entrycachekey(value,entry)letupdate_cachecachekeyentry=letcache=matchlookup_entrycachekeywith|None->cache|Some(_,old_entry)->remove_cache_entrycachekeyold_entryinmatchentrywith|None->cache|Some(entry,size)->insert_cachecachekeyentrysizefresh_entry_nonceletupdatetkeyentry=letcache=cache_of_keytkeyinupdate_cache_withtkey.cache_index(update_cachecachekeyentry)(* We maintain the number of entries removal for the last
[entries_removals_window_width] blocks to determine the life
expectancy of cache entries. *)letentries_removals_window_width=5letmedian_entries_removalscache=letmedianl=List.(nth(sortInt.comparel)(lengthl/2))inmatchmediancache.entries_removalswithNone->0|Somex->xletuninitialised=Noneletkey_of_identifier~cache_indexidentifier={identifier;cache_index}letidentifier_of_key{identifier;_}=identifierletppfmt=function|None->Format.fprintffmt"Unitialised cache"|Somecaches->FunctionalArray.iter(pp_cachefmt)cachesletfindtkey=lookup_value(cache_of_keytkey)keyletcompatible_layouttlayout=with_cachest(funcaches->Compare.List_length_with.(layout=FunctionalArray.lengthcaches)&&List.fold_left_i(funidxrlen->r&&(FunctionalArray.getcachesidx).limit=len)truelayout)letfrom_layoutlayout=Some(make_cacheslayout)letfuture_cache_expectationt~time_in_blocks=letexpectedcache=letoldness=time_in_blocks*median_entries_removalscacheinUtils.fold_n_timesoldnessremove_deancacheinSome(with_cachest(FunctionalArray.mapexpected))letrecord_entries_removalscache=letentries_removals=ifList.compare_length_withcache.entries_removalsentries_removals_window_width>=0thenmatchcache.entries_removalswith|[]->assertfalse|_::entries_removals->entries_removalselsecache.entries_removalsinletentries_removals=entries_removals@[KeySet.cardinalcache.removed_entries]in{cachewithentries_removals;removed_entries=KeySet.empty}(* [update_entry ctxt cache key entry nonce] stores the [entry]
identified by [key] in a [cache] of the context. Each fresh entry
is marked with the [nonce] to characterize the block that has
introduced it. *)letupdate_entryentrynonce=letelement_nonce=ifBytes.equalentry.cache_noncefresh_entry_noncethennonceelseentry.cache_noncein{entrywithcache_nonce=element_nonce}(* [finalize_cache ctxt cache nonce] sets the cache nonce for the new
entries. This function returns the cache for the next block. *)letfinalize_cache({map;_}ascache)nonce=letmap=KeyMap.map(fun(e,entry)->(e,update_entryentrynonce))mapinletmetamap=KeyMap.mapsndmapin({cachewithmap},metamap)(**
A subcache has a domain composed of:
- [keys] to restore the in-memory representation of the subcache at
loading time ;
- [counter] to restart the generation of "birth dates" for new entries
at the right counter.
[counter] is important because restarting from [0] does not work.
Indeed, a baker that reloads the cache from the domain must be
able to reconstruct the exact same cache as the validator. The
validator maintains a cache in memory by inheriting it from the
predecessor block: hence its counter is never reset.
*)typesubcache_domain={keys:value_metadataKeyMap.t;counter:int64}typedomain=subcache_domainlistletsync_cachecache~cache_nonce=letcache=enforce_size_limitcacheinletcache=record_entries_removalscacheinletcache,new_entries=finalize_cachecachecache_noncein(cache,{keys=new_entries;counter=cache.counter})letsubcache_keys_encoding:value_metadataKeyMap.tData_encoding.t=Data_encoding.(convKeyMap.bindings(funb->KeyMap.of_seq(List.to_seqb))(list(dynamic_size(tup2key_encodingvalue_metadata_encoding))))letsubcache_domain_encoding:subcache_domainData_encoding.t=Data_encoding.(conv(fun{keys;counter}->(keys,counter))(fun(keys,counter)->{keys;counter})(obj2(req"keys"subcache_keys_encoding)(req"counter"int64)))letdomain_encoding:domainData_encoding.t=Data_encoding.(listsubcache_domain_encoding)letequal_subdomains1s2=s1.counter=s2.counter&&KeyMap.equalequal_value_metadatas1.keyss2.keysletempty_domain=List.is_emptyletsynct~cache_nonce=with_cachest@@funcaches->FunctionalArray.fold_map(funacccache->letcache,domain=sync_cachecache~cache_noncein(domain::acc,cache))caches[]empty_cache|>fun(rev_domains,caches)->(Somecaches,List.revrev_domains)letupdate_cache_keytkeyvaluemeta=with_cachest@@funcaches->letcache=FunctionalArray.getcacheskey.cache_indexinletcache=insert_cache_entrycachekey(value,meta)inupdate_cache_withtkey.cache_indexcacheletclear_cachecache={index=cache.index;limit=cache.limit;map=KeyMap.empty;size=0;counter=0L;lru=Int64Map.empty;entries_removals=[];removed_entries=KeySet.empty;}letcleart=Some(with_cachest(funcaches->FunctionalArray.mapclear_cachecaches))letfrom_cacheinitialdomain~value_of_key=letdomain'=Array.of_listdomaininletcache=with_caches(clearinitial)@@funcaches->FunctionalArray.mapi(funi(cache:'acache)->ifi=-1thencacheelseifi>=Array.lengthdomain'then(* By precondition: the layout of [domain] and [initial]
must be the same. *)invalid_arg_with_callstack"invalid usage of from_cache"elseletsubdomain=domain'.(i)in{cachewithcounter=subdomain.counter})cachesinletfold_cache_keyssubdomaincache=letopenLwt_result_syntaxinKeyMap.fold_es(funkeyentrycache->let*value=matchlookupinitialkeywith|None->value_of_keykey|Some(value,entry')->ifBytes.equalentry.cache_nonceentry'.cache_noncethenreturnvalueelsevalue_of_keykeyinreturn(update_cache_keycachekeyvalueentry))subdomain.keyscacheinList.fold_left_es(funcachesubdomain->fold_cache_keyssubdomaincache)(Somecache)domainletnumber_of_cachest=with_cachestFunctionalArray.lengthleton_cachetcache_indexf=ifcache_index<number_of_cachest&&cache_index>=0thenSome(f(cache_of_indextcache_index))elseNoneletcache_sizet~cache_index=on_cachetcache_index@@funcache->cache.sizeletcache_size_limitt~cache_index=on_cachetcache_index@@funcache->cache.limitletlist_keyst~cache_index=on_cachetcache_index@@funcache->letxs=KeyMap.fold(funk(_,{size;birth;_})acc->(k,size,birth)::acc)cache.map[]inxs|>List.sort(fun(_,_,b1)(_,_,b2)->Int64.compareb1b2)|>List.map(fun(k,s,_)->(k,s))letkey_rankctxtkey=letcache=cache_of_keyctxtkeyinletreclength_untilxn=function|[]->Somen|y::ys->ifKey.comparexy=0thenSomenelselength_untilx(n+1)ysinifnot@@KeyMap.memkeycache.mapthenNoneelseInt64Map.bindingscache.lru|>List.mapsnd|>length_untilkey0moduleInternal_for_tests=structletequal_domaind1d2=List.equalequal_subdomaind1d2end