123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238(************************************************************************)(* * The Coq Proof Assistant / The Coq Development Team *)(* v * Copyright INRIA, CNRS and contributors *)(* <O___,, * (see version control and CREDITS file for authors & dates) *)(* \VV/ **************************************************************)(* // * This file is distributed under the terms of the *)(* * GNU Lesser General Public License Version 2.1 *)(* * (see LICENSE file for the text of the license) *)(************************************************************************)(* *)(* A persistent hashtable *)(* *)(* Frédéric Besson (Inria Rennes) 2009-2014 *)(* *)(************************************************************************)(** Last PR that requires the regeneration of caches.
It is stored and checked after the Coq magic number.
Incompatible caches are thrown away.
*)letpcache_version=15584lmoduletypePHashtable=sig(* see documentation in [persistent_cache.mli] *)type'attypekeyvalopen_in:string->'atvalfind:'at->key->'avaladd:'at->key->'a->unitvalmemo:string->(key->'a)->key->'avalmemo_cond:string->(key->bool)->(key->'a)->key->'aendopenHashtblopenSystemmodulePHashtable(Key:HashedType):PHashtablewithtypekey=Key.t=structopenUnixtypekey=Key.tmoduleTable:sigtype'atvalempty:'atvaladd:int->'a->'at->'atvalfind:int->'at->'alistend=structtype'at='alistInt.Map.tletempty=Int.Map.emptyletaddhpostab=tryInt.Map.modifyh(fun_l->pos::l)tabwithNot_found->Int.Map.addh[pos]tabletfindhtab=Int.Map.findhtabend(* A mapping key hash -> file position *)type'adata={pos:intoption;mutableobj:(Key.t*'a)option}type'at={outch:out_channel;mutablehtbl:'adataTable.t;file:string}letskip_blobch=lethd=Bytes.createMarshal.header_sizeinlet()=really_inputchhd0Marshal.header_sizeinletlen=Marshal.data_sizehd0inletpos=pos_inchinseek_inch(pos+len)letread_key_eleminch=matchinput_binary_intinchwith|hash->letpos=pos_ininchinlet()=skip_blobinchinSome(hash,pos)|exceptionEnd_of_file->None(**
We used to only lock/unlock regions.
Is-it more robust/portable to lock/unlock a fixed region e.g. [0;1]?
In case of locking failure, the cache is not used.
**)typelock_kind=Read|Writeletlockkdfd=letpos=lseekfd0SEEK_CURinletsuccess=tryignore(lseekfd0SEEK_SET);letlk=matchkdwithRead->F_RLOCK|Write->F_LOCKinlockffdlk1;truewithUnix.Unix_error(_,_,_)->falseinignore(lseekfdposSEEK_SET);successletunlockfd=letpos=lseekfd0SEEK_CURinlet()=tryignore(lseekfd0SEEK_SET);lockffdF_ULOCK1withUnix.Unix_error(_,_,_)->(* Here, this is really bad news --
there is a pending lock which could cause a deadlock.
Should it be an anomaly or produce a warning ?
*)ignore(lseekfdposSEEK_SET)in()(* We make the assumption that an acquired lock can always be released *)letdo_under_lockkdfdf=iflockkdfdthenSome(Fun.protectf~finally:(fun()->unlockfd))elseNoneletfopen_in=open_in_binletcheck_magic_number_and_versioninch=tryletmagic=input_int32inchinletversion=input_int32inchinmagic=ObjFile.magic_number&&version=pcache_versionwithEnd_of_file->falseletopen_in(typea)f:at=letflags=[O_RDONLY;O_CREAT]inletfinch=openfilefflags0o666inletinch=in_channel_of_descrfinchinletexceptionInvalidTableFormatinletrecxloadtable=matchread_key_eleminchwith|None->table|Some(hash,pos)->xload(Table.addhash{pos=Somepos;obj=None}table)|exceptionewhenCErrors.noncriticale->raiseInvalidTableFormatintry(* Locking of the (whole) file while reading *)lethtbl=do_under_lockReadfinch(fun()->lettable=Table.emptyinifcheck_magic_number_and_versioninchthenxloadtableelseraiseInvalidTableFormat)inlet()=close_in_noerrinchinletoutch=out_channel_of_descr(openfilef[O_WRONLY;O_APPEND;O_CREAT]0o666)in{outch;file=f;htbl=Option.defaultTable.emptyhtbl}withInvalidTableFormat->let()=close_in_noerrinchinletflags=[O_WRONLY;O_TRUNC;O_APPEND;O_CREAT]inletout=openfilefflags0o666inletoutch=out_channel_of_descroutinoutput_int32outchObjFile.magic_number;output_int32outchpcache_version;{outch;htbl=Table.empty;file=f}letaddtke=let{outch}=tinletfd=descr_of_out_channeloutchinleth=Key.hashkland0x7FFFFFFFinletdump()=let()=output_binary_intoutchhinlet()=Marshal.to_channeloutch(k,e)[]inlet()=flushoutchin()inlet_=do_under_lockWritefddumpint.htbl<-Table.addh{pos=None;obj=Some(k,e)}t.htblletfindtk=let{outch;htbl=tbl}=tinleth=Key.hashkland0x7FFFFFFFinletlpos=Table.findhtblin(* First look for already live data *)letfinddata=matchdata.objwith|Some(k',v)->ifKey.equalkk'thenSomevelseNone|None->NoneinmatchCList.find_mapfindlposwith|Someres->res|None->(* Otherwise perform I/O and look at the disk cache *)letlpos=List.filter(fundata->Option.is_emptydata.obj)lposinlet()=ifCList.is_emptylposthenraiseNot_foundinletch=fopen_int.fileinletfinddata=matchdata.poswith|None->None|Somepos->let()=seek_inchposinmatchMarshal.from_channelchwith|(k',v)->ifKey.equalkk'then(* Store the data in memory *)let()=data.obj<-Some(k,v)inSomevelseNone|exceptionEnd_of_file|exceptionFailure_->Noneinletlookup()=CList.find_mapfindlposinletres=do_under_lockRead(descr_of_in_channelch)lookupinlet()=close_in_noerrchinmatchreswith|Some(Somev)->v|None|SomeNone->raiseNot_foundletmemocachef=lettbl=lazy(trySome(open_incache)withewhenCErrors.noncriticale->None)infunx->matchLazy.forcetblwith|None->fx|Sometbl->(tryfindtblxwithNot_found->letres=fxinaddtblxres;res)letmemo_condcachecondf=lettbl=lazy(trySome(open_incache)withewhenCErrors.noncriticale->None)infunx->matchLazy.forcetblwith|None->fx|Sometbl->ifcondxthenbegintryfindtblxwithNot_found->letres=fxinaddtblxres;resendelsefxend(* Local Variables: *)(* coding: utf-8 *)(* End: *)