Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file sharedMem.ml
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268(**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the "hack" directory of this source tree.
*
*)openHack_core(* Don't change the ordering of this record without updating hh_shared_init in
* hh_shared.c, which indexes into config objects *)typeconfig={global_size:int;heap_size:int;dep_table_pow:int;hash_table_pow:int;shm_dirs:stringlist;shm_min_avail:int;log_level:int;}(* Allocated in C only. *)typehandle=private{h_fd:Unix.file_descr;h_global_size:int;h_heap_size:int;}letkind_of_intx=matchxwith|0->`ConstantK|1->`ClassK|2->`FuncK|_whenx<0->failwith"kind_of_int: attempted to convert from negative int"|_->assert(x>0);failwith"kind_of_int: int too large, no corresponding kind"let_kind_of_int=kind_of_intexceptionOut_of_shared_memoryexceptionHash_table_fullexceptionDep_table_fullexceptionHeap_fullexceptionRevision_length_is_zeroexceptionSql_assertion_failureofintexceptionFailed_anonymous_memfd_initexceptionLess_than_minimum_availableofintexceptionFailed_to_use_shm_dirofstringexceptionC_assertion_failureofstringlet()=Callback.register_exception"out_of_shared_memory"Out_of_shared_memory;Callback.register_exception"hash_table_full"Hash_table_full;Callback.register_exception"dep_table_full"Dep_table_full;Callback.register_exception"heap_full"Heap_full;Callback.register_exception"revision_length_is_zero"Revision_length_is_zero;Callback.register_exception"sql_assertion_failure"(Sql_assertion_failure0);Callback.register_exception"failed_anonymous_memfd_init"Failed_anonymous_memfd_init;Callback.register_exception"less_than_minimum_available"(Less_than_minimum_available0);Callback.register_exception"c_assertion_failure"(C_assertion_failure"dummy string")(*****************************************************************************)(* Initializes the shared memory. Must be called before forking. *)(*****************************************************************************)externalhh_shared_init:config:config->shm_dir:stringoption->handle="hh_shared_init"letanonymous_initconfig=hh_shared_init~config~shm_dir:Noneletrecshm_dir_initconfig=function|[]->Hh_logger.log"We've run out of filesystems to use for shared memory";raiseOut_of_shared_memory|shm_dir::shm_dirs->letshm_min_avail=config.shm_min_availinbegintry(* For some reason statvfs is segfaulting when the directory doesn't
* exist, instead of returning -1 and an errno *)ifnot(Sys.file_existsshm_dir)thenraise(Failed_to_use_shm_dir"shm_dir does not exist");hh_shared_init~config~shm_dir:(Someshm_dir)with|Less_than_minimum_availableavail->EventLogger.(log_if_initialized(fun()->sharedmem_less_than_minimum_available~shm_dir~shm_min_avail~avail));if!Utils.debugthenHh_logger.log"Filesystem %s only has %d bytes available, \
which is less than the minimum %d bytes"shm_diravailconfig.shm_min_avail;shm_dir_initconfigshm_dirs|Unix.Unix_error(e,fn,arg)->letfn_string=iffn=""then""elseUtils.spf" thrown by %s(%s)"fnarginletreason=Utils.spf"Unix error%s: %s"fn_string(Unix.error_messagee)inEventLogger.(log_if_initialized(fun()->sharedmem_failed_to_use_shm_dir~shm_dir~reason));if!Utils.debugthenHh_logger.log"Failed to use shm dir `%s`: %s"shm_dirreason;shm_dir_initconfigshm_dirs|Failed_to_use_shm_dirreason->EventLogger.(log_if_initialized(fun()->sharedmem_failed_to_use_shm_dir~shm_dir~reason));if!Utils.debugthenHh_logger.log"Failed to use shm dir `%s`: %s"shm_dirreason;shm_dir_initconfigshm_dirsendletinitconfig=tryanonymous_initconfigwithFailed_anonymous_memfd_init->EventLogger.(log_if_initialized(fun()->sharedmem_failed_anonymous_memfd_init()));if!Utils.debugthenHh_logger.log"Failed to use anonymous memfd init";shm_dir_initconfigconfig.shm_dirsexternalconnect:handle->unit="hh_connect"(*****************************************************************************)(* The shared memory garbage collector. It must be called every time we
* free data (cf hh_shared.c for the underlying C implementation).
*)(*****************************************************************************)externalhh_collect:unit->unit="hh_collect"[@@noalloc](*****************************************************************************)(* Serializes the dependency table and writes it to a file *)(*****************************************************************************)externalloaded_dep_table_filename_c:unit->string="hh_get_loaded_dep_table_filename"letloaded_dep_table_filename()=letfn=loaded_dep_table_filename_c()inifString.equal""fnthenNoneelseSomefn(** Returns number of dependency edges added. *)externalsave_dep_table_sqlite_c:string->string->int="hh_save_dep_table_sqlite"letsave_dep_table_sqlite:string->string->int=funfnbuild_revision->if(loaded_dep_table_filename())<>Nonethenfailwith"save_dep_table_sqlite not supported when server is loaded from a saved state";Hh_logger.log"Dumping a saved state deptable.";save_dep_table_sqlite_cfnbuild_revision(*****************************************************************************)(* Loads the dependency table by reading from a file *)(*****************************************************************************)externalload_dep_table_sqlite_c:string->bool->int="hh_load_dep_table_sqlite"letload_dep_table_sqlite:string->bool->int=funfnignore_hh_version->load_dep_table_sqlite_cfnignore_hh_version(*****************************************************************************)(* Serializes & loads the hash table directly into memory *)(*****************************************************************************)externalsave_table:string->unit="hh_save_table"externalload_table:string->unit="hh_load_table"(*****************************************************************************)(* Serializes the hash table to sqlite *)(*****************************************************************************)externalhh_save_table_sqlite:string->int="hh_save_table_sqlite"letsave_table_sqlitefilename=hh_save_table_sqlitefilenameexternalhh_save_table_keys_sqlite:string->stringarray->int="hh_save_table_keys_sqlite"letsave_table_keys_sqlitefilenamekeys=hh_save_table_keys_sqlitefilenamekeys(*****************************************************************************)(* Loads the hash table by reading from a file *)(*****************************************************************************)externalhh_load_table_sqlite:string->bool->int="hh_load_table_sqlite"letload_table_sqlitefilenameverify=hh_load_table_sqlitefilenameverify(*****************************************************************************)(* Cleans up the artifacts generated by SQL *)(*****************************************************************************)externalcleanup_sqlite:unit->unit="hh_cleanup_sqlite"(*****************************************************************************)(* The size of the dynamically allocated shared memory section *)(*****************************************************************************)externalheap_size:unit->int="hh_used_heap_size"[@@noalloc](*****************************************************************************)(* Part of the heap not reachable from hashtable entries. *)(*****************************************************************************)externalwasted_heap_size:unit->int="hh_wasted_heap_size"[@@noalloc](*****************************************************************************)(* The logging level for shared memory statistics *)(* 0 = nothing *)(* 1 = log totals, averages, min, max bytes marshalled and unmarshalled *)(*****************************************************************************)externalhh_log_level:unit->int="hh_log_level"[@@noalloc](*****************************************************************************)(* The number of used slots in our hashtable *)(*****************************************************************************)externalhash_used_slots:unit->int*int="hh_hash_used_slots"(*****************************************************************************)(* The total number of slots in our hashtable *)(*****************************************************************************)externalhash_slots:unit->int="hh_hash_slots"(*****************************************************************************)(* The number of used slots in our dependency table *)(*****************************************************************************)externaldep_used_slots:unit->int="hh_dep_used_slots"(*****************************************************************************)(* The total number of slots in our dependency table *)(*****************************************************************************)externaldep_slots:unit->int="hh_dep_slots"(*****************************************************************************)(* Must be called after the initialization of the hack server is over.
* (cf serverInit.ml). *)(*****************************************************************************)externalhh_check_heap_overflow:unit->bool="hh_check_heap_overflow"letinit_done()=EventLogger.sharedmem_init_done(heap_size())typetable_stats={nonempty_slots:int;used_slots:int;slots:int;}letdep_stats()=letused=dep_used_slots()in{nonempty_slots=used;used_slots=used;slots=dep_slots();}lethash_stats()=letused_slots,nonempty_slots=hash_used_slots()in{nonempty_slots;used_slots;slots=hash_slots();}letshould_collect(effort:[`gentle|`aggressive|`always_TEST])=letoverhead=matcheffortwith|`always_TEST->1.0|`aggressive->1.2|`gentle->2.0inletused=heap_size()inletwasted=wasted_heap_size()inletreachable=used-wastedinused>=truncate((floatreachable)*.overhead)letcollect(effort:[`gentle|`aggressive|`always_TEST])=letold_size=heap_size()inStats.update_max_heap_sizeold_size;letstart_t=Unix.gettimeofday()in(* The wrapper is used to run the function in a worker instead of master. *)ifshould_collecteffortthenhh_collect();letnew_size=heap_size()inlettime_taken=Unix.gettimeofday()-.start_tinifold_size<>new_sizethenbeginEventLogger.sharedmem_gc_raneffortold_sizenew_sizetime_takenendletis_heap_overflow()=hh_check_heap_overflow()(*****************************************************************************)(* Compute size of values in the garbage-collected heap *)(*****************************************************************************)moduleHeapSize=structletrectraverse((visited:ISet.t),acc)r=ifObj.is_blockrthenbeginletp:int=Obj.magicrinifISet.mempvisitedthen(visited,acc)elsebeginletvisited'=ISet.addpvisitedinletn=Obj.sizerinletacc'=acc+1+ninifObj.tagr<Obj.no_scan_tagthentraverse_fields(visited',acc')rnelse(visited',acc')endendelse(visited,acc)andtraverse_fieldsaccri=leti=i-1inifi<0thenaccelsetraverse_fields(traverseacc(Obj.fieldri))ri(* Return size in bytes that o occupies in GC heap *)letsizer=let(_,w)=traverse(ISet.empty,0)rinw*(Sys.word_size/8)endletvalue_size=HeapSize.size(*****************************************************************************)(* Module returning the MD5 of the key. It's because the code in C land
* expects this format. I prefer to make it an abstract type to make sure
* we don't forget to give the MD5 instead of the key itself.
*)(*****************************************************************************)moduletypeKey=sig(* The type of keys that OCaml-land callers try to insert *)typeuserkey(* The type of keys that get stored in the C hashtable *)typet(* The type of old keys that get stored in the C hashtable *)typeold(* The md5 of an old or a new key *)typemd5(* Creation/conversion primitives *)valmake:Prefix.t->userkey->tvalmake_old:Prefix.t->userkey->oldvalto_old:t->oldvalnew_from_old:old->t(* Md5 primitives *)valmd5:t->md5valmd5_old:old->md5valstring_of_md5:md5->stringendmoduleKeyFunctor(UserKeyType:sigtypetvalto_string:t->stringend):Keywithtypeuserkey=UserKeyType.t=structtypeuserkey=UserKeyType.ttypet=stringtypeold=stringtypemd5=string(* The prefix we use for old keys. The prefix guarantees that we never
* mix old and new data, because a key can never start with the prefix
* "old_", it always starts with a number (cf Prefix.make()).
*)letold_prefix="old_"letmakeprefixx=Prefix.make_keyprefix(UserKeyType.to_stringx)letmake_oldprefixx=old_prefix^Prefix.make_keyprefix(UserKeyType.to_stringx)letto_oldx=old_prefix^xletnew_from_oldx=letmoduleS=StringinS.subx(S.lengthold_prefix)(S.lengthx-S.lengthold_prefix)letmd5=Digest.stringletmd5_old=Digest.stringletstring_of_md5x=xend(*****************************************************************************)(* Raw interface to shared memory (cf hh_shared.c for the underlying
* representation).
*)(*****************************************************************************)moduleRaw(Key:Key)(Value:Value.Type):sigvaladd:Key.md5->Value.t->unitvalmem:Key.md5->boolvalget:Key.md5->Value.tvalremove:Key.md5->unitvalmove:Key.md5->Key.md5->unitmoduleLocalChanges:sigvalhas_local_changes:unit->boolvalpush_stack:unit->unitvalpop_stack:unit->unitvalrevert:Key.md5->unitvalcommit:Key.md5->unitvalrevert_all:unit->unitvalcommit_all:unit->unitendend=struct(* Returns the number of bytes allocated in the heap, or a negative number
* if no new memory was allocated *)externalhh_add:Key.md5->Value.t->int*int="hh_add"externalhh_mem:Key.md5->bool="hh_mem"externalhh_mem_status:Key.md5->int="hh_mem_status"externalhh_get_size:Key.md5->int="hh_get_size"externalhh_get_and_deserialize:Key.md5->Value.t="hh_get_and_deserialize"externalhh_remove:Key.md5->unit="hh_remove"externalhh_move:Key.md5->Key.md5->unit="hh_move"lethh_mem_statusx=WorkerCancel.with_worker_exit(fun()->hh_mem_statusx)let_=hh_mem_statuslethh_memx=WorkerCancel.with_worker_exit(fun()->hh_memx)lethh_addxy=WorkerCancel.with_worker_exit(fun()->hh_addxy)lethh_get_and_deserializex=WorkerCancel.with_worker_exit(fun()->hh_get_and_deserializex)letlog_serializecompressedoriginal=letcompressed=floatcompressedinletoriginal=floatoriginalinletsaved=original-.compressedinletratio=compressed/.originalinMeasure.sample(Value.description^" (bytes serialized into shared heap)")compressed;Measure.sample("ALL bytes serialized into shared heap")compressed;Measure.sample(Value.description^" (bytes saved in shared heap due to compression)")saved;Measure.sample("ALL bytes saved in shared heap due to compression")saved;Measure.sample(Value.description^" (shared heap compression ratio)")ratio;Measure.sample("ALL bytes shared heap compression ratio")ratioletlog_deserializelr=letsharedheap=floatlinMeasure.sample(Value.description^" (bytes deserialized from shared heap)")sharedheap;Measure.sample("ALL bytes deserialized from shared heap")sharedheap;ifhh_log_level()>1thenbegin(* value_size is a bit expensive to call this often, so only run with log levels >= 2 *)letlocalheap=float(value_sizer)inMeasure.sample(Value.description^" (bytes allocated for deserialized value)")localheap;Measure.sample("ALL bytes allocated for deserialized value")localheapend(**
* Represents a set of local changes to the view of the shared memory heap
* WITHOUT materializing to the changes in the actual heap. This allows us to
* make speculative changes to the view of the world that can be reverted
* quickly and correctly.
*
* A LocalChanges maintains the same invariants as the shared heap. Except
* add are allowed to overwrite filled keys. This is for convenience so we
* do not need to remove filled keys upfront.
*
* LocalChanges can be committed. This will apply the changes to the previous
* stack, or directly to shared memory if there are no other active stacks.
* Since changes are kept local to the process, this is NOT compatible with
* the parallelism provided by MultiWorker.ml
*)moduleLocalChanges=structtypeaction=(* The value does not exist in the current stack. When committed this
* action will invoke remove on the previous stack.
*)|Remove(* The value is added to a previously empty slot. When committed this
* action will invoke add on the previous stack.
*)|AddofValue.t(* The value is replacing a value already associated with a key in the
* previous stack. When committed this action will invoke remove then
* add on the previous stack.
*)|ReplaceofValue.ttypet={current:(Key.md5,action)Hashtbl.t;prev:toption;}letstack:toptionref=refNonelethas_local_changes()=Core_kernel.Option.is_some(!stack)letrecmemstack_optkey=matchstack_optwith|None->hh_memkey|Somestack->tryHashtbl.findstack.currentkey<>RemovewithNot_found->memstack.prevkeyletrecgetstack_optkey=matchstack_optwith|None->letv=hh_get_and_deserializekeyinifhh_log_level()>0then(log_deserialize(hh_get_sizekey)(Obj.reprv));v|Somestack->trymatchHashtbl.findstack.currentkeywith|Remove->failwith"Trying to get a non-existent value"|Replacevalue|Addvalue->valuewithNot_found->getstack.prevkey(**
* For remove/add it is best to think of them in terms of a state machine.
* A key can be in the following states:
*
* Remove:
* Local changeset removes a key from the previous stack
* Replace:
* Local changeset replaces value of a key in previous stack
* Add:
* Local changeset associates a value with a key. The key is not
* present in the previous stacks
* Empty:
* No local changes and key is not present in previous stack
* Filled:
* No local changes and key has an associated value in previous stack
* *Error*:
* This means an exception will occur
*
*
* Transitions table:
* Remove -> *Error*
* Replace -> Remove
* Add -> Empty
* Empty -> *Error*
* Filled -> Remove
*)letremovestack_optkey=matchstack_optwith|None->hh_removekey|Somestack->trymatchHashtbl.findstack.currentkeywith|Remove->failwith"Trying to remove a non-existent value"|Replace_->Hashtbl.replacestack.currentkeyRemove|Add_->Hashtbl.removestack.currentkeywithNot_found->ifmemstack.prevkeythenHashtbl.replacestack.currentkeyRemoveelsefailwith"Trying to remove a non-existent value"(**
* Transitions table:
* Remove -> Replace
* Replace -> Replace
* Add -> Add
* Empty -> Add
* Filled -> Replace
*)letaddstack_optkeyvalue=matchstack_optwith|None->letcompressed_size,original_size=hh_addkeyvalueinifhh_log_level()>0&&compressed_size>0thenlog_serializecompressed_sizeoriginal_size|Somestack->trymatchHashtbl.findstack.currentkeywith|Remove|Replace_->Hashtbl.replacestack.currentkey(Replacevalue)|Add_->Hashtbl.replacestack.currentkey(Addvalue)withNot_found->ifmemstack.prevkeythenHashtbl.replacestack.currentkey(Replacevalue)elseHashtbl.replacestack.currentkey(Addvalue)letmovestack_optfrom_keyto_key=matchstack_optwith|None->hh_movefrom_keyto_key|Some_stack->assert(memstack_optfrom_key);assert(not@@memstack_optto_key);letvalue=getstack_optfrom_keyinremovestack_optfrom_key;addstack_optto_keyvalueletcommit_actionchangesetkeyelem=matchelemwith|Remove->removechangesetkey|Addvalue->addchangesetkeyvalue|Replacevalue->removechangesetkey;addchangesetkeyvalue(** Public API **)letpush_stack()=stack:=Some({current=Hashtbl.create128;prev=!stack;})letpop_stack()=match!stackwith|None->failwith"There are no active local change stacks. Nothing to pop!"|Some{prev;_}->stack:=prevletrevertkey=match!stackwith|None->()|Somechangeset->Hashtbl.removechangeset.currentkeyletcommitkey=match!stackwith|None->()|Somechangeset->trycommit_actionchangeset.prevkey@@Hashtbl.findchangeset.currentkeywithNot_found->()letrevert_all()=match!stackwith|None->()|Somechangeset->Hashtbl.clearchangeset.currentletcommit_all()=match!stackwith|None->()|Somechangeset->Hashtbl.iter(commit_actionchangeset.prev)changeset.currentendletaddkeyvalue=LocalChanges.(add!stackkeyvalue)letmemkey=LocalChanges.(mem!stackkey)letgetkey=LocalChanges.(get!stackkey)letremovekey=LocalChanges.(remove!stackkey)letmovefrom_keyto_key=LocalChanges.(move!stackfrom_keyto_key)end(*****************************************************************************)(* Module used to access "new" values (as opposed to old ones).
* There are several cases where we need to compare the old and the new
* representation of objects (to determine what has changed).
* The "old" representation is the value that was bound to that key in the
* last round of type-checking.
* Despite the fact that the same storage is used under the hood, it's good
* to separate the two interfaces to make sure we never mix old and new
* values.
*)(*****************************************************************************)moduleNew:functor(Key:Key)->functor(Value:Value.Type)->sig(* Adds a binding to the table, the table is left unchanged if the
* key was already bound.
*)valadd:Key.t->Value.t->unitvalget:Key.t->Value.toptionvalfind_unsafe:Key.t->Value.tvalremove:Key.t->unitvalmem:Key.t->bool(* Binds the key to the old one.
* If 'mykey' is bound to 'myvalue', oldifying 'mykey' makes 'mykey'
* accessible to the "Old" module, in other words: "Old.mem mykey" returns
* true and "New.mem mykey" returns false after oldifying.
*)valoldify:Key.t->unitmoduleRaw:moduletypeofRaw(Key)(Value)end=functor(Key:Key)->functor(Value:Value.Type)->structmoduleRaw=Raw(Key)(Value)letaddkeyvalue=Raw.add(Key.md5key)valueletmemkey=Raw.mem(Key.md5key)letgetkey=letkey=Key.md5keyinifRaw.memkeythenSome(Raw.getkey)elseNoneletfind_unsafekey=matchgetkeywith|None->raiseNot_found|Somex->xletremovekey=letkey=Key.md5keyinifRaw.memkeythenbeginRaw.removekey;assert(not(Raw.memkey));endelse()letoldifykey=ifmemkeythenletold_key=Key.to_oldkeyinRaw.move(Key.md5key)(Key.md5_oldold_key)else()end(* Same as new, but for old values *)moduleOld:functor(Key:Key)->functor(Value:Value.Type)->functor(Raw:moduletypeofRaw(Key)(Value))->sigvalget:Key.old->Value.toptionvalremove:Key.old->unitvalmem:Key.old->bool(* Takes an old value and moves it back to a "new" one *)valrevive:Key.old->unitend=functor(Key:Key)->functor(Value:Value.Type)->functor(Raw:moduletypeofRaw(Key)(Value))->structletgetkey=letkey=Key.md5_oldkeyinifRaw.memkeythenSome(Raw.getkey)elseNoneletmemkey=Raw.mem(Key.md5_oldkey)letremovekey=ifmemkeythenRaw.remove(Key.md5_oldkey)letrevivekey=ifmemkeythenletnew_key=Key.new_from_oldkeyinletnew_key=Key.md5new_keyinletold_key=Key.md5_oldkeyinifRaw.memnew_keythenRaw.removenew_key;Raw.moveold_keynew_keyend(*****************************************************************************)(* The signatures of what we are actually going to expose to the user *)(*****************************************************************************)moduletypeNoCache=sigtypekeytypetmoduleKeySet:Set.Swithtypeelt=keymoduleKeyMap:MyMap.Swithtypekey=keyvaladd:key->t->unitvalget:key->toptionvalget_old:key->toptionvalget_old_batch:KeySet.t->toptionKeyMap.tvalremove_old_batch:KeySet.t->unitvalfind_unsafe:key->tvalget_batch:KeySet.t->toptionKeyMap.tvalremove_batch:KeySet.t->unitvalstring_of_key:key->stringvalmem:key->boolvalmem_old:key->boolvaloldify_batch:KeySet.t->unitvalrevive_batch:KeySet.t->unitmoduleLocalChanges:sigvalhas_local_changes:unit->boolvalpush_stack:unit->unitvalpop_stack:unit->unitvalrevert_batch:KeySet.t->unitvalcommit_batch:KeySet.t->unitvalrevert_all:unit->unitvalcommit_all:unit->unitendendmoduletypeWithCache=sigincludeNoCachevalwrite_through:key->t->unitvalget_no_cache:key->toptionend(*****************************************************************************)(* The interface that all keys need to implement *)(*****************************************************************************)moduletypeUserKeyType=sigtypetvalto_string:t->stringvalcompare:t->t->intend(*****************************************************************************)(* A functor returning an implementation of the S module without caching. *)(*****************************************************************************)moduleNoCache(UserKeyType:UserKeyType)(Value:Value.Type)=structmoduleKey=KeyFunctor(UserKeyType)moduleNew=New(Key)(Value)moduleOld=Old(Key)(Value)(New.Raw)moduleKeySet=Set.Make(UserKeyType)moduleKeyMap=MyMap.Make(UserKeyType)typekey=UserKeyType.ttypet=Value.tletstring_of_keykey=key|>Key.makeValue.prefix|>Key.md5|>Key.string_of_md5;;letaddxy=New.add(Key.makeValue.prefixx)yletfind_unsafex=New.find_unsafe(Key.makeValue.prefixx)letgetx=trySome(find_unsafex)withNot_found->Noneletget_oldx=letkey=Key.make_oldValue.prefixxinOld.getkeyletget_old_batchxs=KeySet.foldbeginfunstr_keyacc->letkey=Key.make_oldValue.prefixstr_keyinKeyMap.addstr_key(Old.getkey)accendxsKeyMap.emptyletremove_batchxs=KeySet.iterbeginfunstr_key->letkey=Key.makeValue.prefixstr_keyinNew.removekeyendxsletoldify_batchxs=KeySet.iterbeginfunstr_key->letkey=Key.makeValue.prefixstr_keyinifNew.memkeythenNew.oldifykeyelseletkey=Key.make_oldValue.prefixstr_keyinOld.removekeyendxsletrevive_batchxs=KeySet.iterbeginfunstr_key->letold_key=Key.make_oldValue.prefixstr_keyinifOld.memold_keythenOld.reviveold_keyelseletkey=Key.makeValue.prefixstr_keyinNew.removekeyendxsletget_batchxs=KeySet.foldbeginfunstr_keyacc->letkey=Key.makeValue.prefixstr_keyinmatchNew.getkeywith|None->KeyMap.addstr_keyNoneacc|Somedata->KeyMap.addstr_key(Somedata)accendxsKeyMap.emptyletmemx=New.mem(Key.makeValue.prefixx)letmem_oldx=Old.mem(Key.make_oldValue.prefixx)letremove_old_batchxs=KeySet.iterbeginfunstr_key->letkey=Key.make_oldValue.prefixstr_keyinOld.removekeyendxsmoduleLocalChanges=structincludeNew.Raw.LocalChangesletrevert_batchkeys=KeySet.iterbeginfunstr_key->letkey=Key.makeValue.prefixstr_keyinrevert(Key.md5key)endkeysletcommit_batchkeys=KeySet.iterbeginfunstr_key->letkey=Key.makeValue.prefixstr_keyincommit(Key.md5key)endkeysendend(*****************************************************************************)(* All the cache are configured by a module of type ConfigType *)(*****************************************************************************)moduletypeConfigType=sig(* The type of object we want to keep in cache *)typevalue(* The capacity of the cache *)valcapacity:intend(*****************************************************************************)(* All the caches are functors returning a module of the following signature
*)(*****************************************************************************)moduletypeCacheType=sigtypekeytypevaluevaladd:key->value->unitvalget:key->valueoptionvalremove:key->unitvalclear:unit->unitvalstring_of_key:key->stringvalget_size:unit->intend(*****************************************************************************)(* Cache keeping the objects the most frequently used. *)(*****************************************************************************)moduleFreqCache(Key:sigtypetend)(Config:ConfigType):CacheTypewithtypekey:=Key.tandtypevalue:=Config.value=structtypevalue=Config.valueletstring_of_key_key=failwith"FreqCache does not support 'string_of_key'"(* The cache itself *)let(cache:(Key.t,intref*value)Hashtbl.t)=Hashtbl.create(2*Config.capacity)letsize=ref0letget_size()=!sizeletclear()=Hashtbl.clearcache;size:=0(* The collection function is called when we reach twice original
* capacity in size. When the collection is triggered, we only keep
* the most frequently used objects.
* So before collection: size = 2 * capacity
* After collection: size = capacity (with the most frequently used objects)
*)letcollect()=if!size<2*Config.capacitythen()elseletl=ref[]inHashtbl.iterbeginfunkey(freq,v)->l:=(key,!freq,v)::!lendcache;Hashtbl.clearcache;l:=List.sort~cmp:(fun(_,x,_)(_,y,_)->y-x)!l;leti=ref0inwhile!i<Config.capacitydomatch!lwith|[]->i:=Config.capacity|(k,_freq,v)::rl->Hashtbl.replacecachek(ref0,v);l:=rl;incri;done;size:=Config.capacity;()letaddxy=collect();tryletfreq,y'=Hashtbl.findcachexinincrfreq;ify'==ythen()elseHashtbl.replacecachex(freq,y)withNot_found->incrsize;letelt=ref0,yinHashtbl.replacecachexelt;()letfindx=letfreq,value=Hashtbl.findcachexinincrfreq;valueletgetx=trySome(findx)withNot_found->Noneletremovex=ifHashtbl.memcachexthendecrsize;Hashtbl.removecachexend(*****************************************************************************)(* An ordered cache keeps the most recently used objects *)(*****************************************************************************)moduleOrderedCache(Key:sigtypetend)(Config:ConfigType):CacheTypewithtypekey:=Key.tandtypevalue:=Config.value=structletstring_of_key_key=failwith"OrderedCache does not support 'string_of_key'"let(cache:(Key.t,Config.value)Hashtbl.t)=Hashtbl.createConfig.capacityletqueue=Queue.create()letsize=ref0letget_size()=!sizeletclear()=Hashtbl.clearcache;size:=0;Queue.clearqueue;()letaddxy=if!size>=Config.capacitythenbegin(* Remove oldest element - if it's still around. *)letelt=Queue.popqueueinifHashtbl.memcacheeltthenbegindecrsize;Hashtbl.removecacheeltend;end;(* Add the new element, but bump the size only if it's a new addition. *)Queue.pushxqueue;ifnot(Hashtbl.memcachex)thenincrsize;Hashtbl.replacecachexyletfindx=Hashtbl.findcachexletgetx=trySome(findx)withNot_found->Noneletremovex=tryifHashtbl.memcachexthendecrsize;Hashtbl.removecachex;withNot_found->()end(*****************************************************************************)(* Every time we create a new cache, a function that knows how to clear the
* cache is registered in the "invalidate_callback_list" global.
*)(*****************************************************************************)letinvalidate_callback_list=ref[]letinvalidate_caches()=List.iter!invalidate_callback_list~f:beginfuncallback->callback()endmoduleLocalCache(UserKeyType:UserKeyType)(Value:Value.Type)=structtypekey=UserKeyType.ttypevalue=Value.tmoduleConfValue=structtypevalue=Value.tletcapacity=1000end(* Young values cache *)moduleL1=OrderedCache(UserKeyType)(ConfValue)(* Frequent values cache *)moduleL2=FreqCache(UserKeyType)(ConfValue)letstring_of_key_key=failwith"LocalCache does not support 'string_of_key'"letaddxy=L1.addxy;L2.addxyletgetx=matchL1.getxwith|None->(matchL2.getxwith|None->None|Somevasresult->L1.addxv;result)|Somevasresult->L2.addxv;resultletremovex=L1.removex;L2.removexletclear()=L1.clear();L2.clear()let()=invalidate_callback_list:=beginfun()->L1.clear();L2.clear()end::!invalidate_callback_listletget_size()=L1.get_size()+L2.get_size()end(*****************************************************************************)(* A functor returning an implementation of the S module with caching.
* We need to avoid constantly deserializing types, because it costs us too
* much time. The caches keep a deserialized version of the types.
*)(*****************************************************************************)moduleWithCache(UserKeyType:UserKeyType)(Value:Value.Type)=structmoduleDirect=NoCache(UserKeyType)(Value)typekey=Direct.keytypet=Direct.tmoduleKeySet=Direct.KeySetmoduleKeyMap=Direct.KeyMapmoduleCache=LocalCache(UserKeyType)(Value)letstring_of_keykey=Direct.string_of_keykeyletaddxy=Direct.addxy;Cache.addxyletget_no_cache=Direct.getletwrite_throughxy=(* Note that we do not need to do any cache invalidation here because
* Direct.add is a no-op if the key already exists. *)Direct.addxyletlog_hit_rate~hit=Measure.sample(Value.description^" (cache hit rate)")(ifhitthen1.else0.);Measure.sample("(ALL cache hit rate)")(ifhitthen1.else0.)letgetx=matchCache.getxwith|None->letresult=(matchDirect.getxwith|None->None|Somevasresult->Cache.addxv;result)inifhh_log_level()>0thenlog_hit_rate~hit:false;result|Some_asresult->ifhh_log_level()>0thenlog_hit_rate~hit:true;result(* We don't cache old objects, they are not accessed often enough. *)letget_old=Direct.get_oldletget_old_batch=Direct.get_old_batchletmem_old=Direct.mem_oldletfind_unsafex=matchgetxwith|None->raiseNot_found|Somex->xletmemx=matchgetxwith|None->false|Some_->trueletget_batchkeys=KeySet.foldbeginfunkeyacc->KeyMap.addkey(getkey)accendkeysKeyMap.emptyletoldify_batchkeys=Direct.oldify_batchkeys;KeySet.iterCache.removekeysletrevive_batchkeys=Direct.revive_batchkeys;KeySet.iterCache.removekeysletremove_batchxs=Direct.remove_batchxs;KeySet.iterCache.removexslet()=invalidate_callback_list:=beginfun()->Cache.clear()end::!invalidate_callback_listletremove_old_batch=Direct.remove_old_batchmoduleLocalChanges=structletpush_stack()=Direct.LocalChanges.push_stack();Cache.clear()letpop_stack()=Direct.LocalChanges.pop_stack();Cache.clear()letrevert_batchkeys=Direct.LocalChanges.revert_batchkeys;KeySet.iterCache.removekeysletcommit_batchkeys=Direct.LocalChanges.commit_batchkeys;KeySet.iterCache.removekeysletrevert_all()=Direct.LocalChanges.revert_all();Cache.clear()letcommit_all()=Direct.LocalChanges.commit_all();Cache.clear()lethas_local_changes()=Direct.LocalChanges.has_local_changes()endend