Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file ocsigen_cache.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554(* Ocsigen
* Copyright (C) 2009
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)(**
Cache.
@author Vincent Balat
@author Raphaël Proust (adding timers)
*)let(>>=)=Lwt.bindmoduleDlist:sigtype'attype'anodevalcreate:?timer:float->int->'atvaladd:'a->'at->'aoptionvalnewest:'at->'anodeoptionvaloldest:'at->'anodeoption(** Removes an element from its list.
If the element is not in a list, it does nothing.
If it is in a list, it calls the finaliser, then removes the element.
If the finaliser fails with an exception,
the element is removed and the exception is raised again.
*)valremove:'anode->unit(** Removes the element from its list without finalising,
then adds it as newest. *)valup:'anode->unitvalsize:'at->intvalmaxsize:'at->int(** returns the timer of the Dlist *)valget_timer:'at->floatoptionvalvalue:'anode->'avallist_of:'anode->'atoption(** remove the n oldest values ;
returns the list of removed values *)valremove_n_oldest:'at->int->'alist(** fold over the elements from the cache starting from the newest
to the oldest *)valfold:('b->'a->'b)->'b->'at->'b(** fold over the elements from the cache starting from the oldest
to the newest *)valfold_back:('b->'a->'b)->'b->'at->'b(** lwt version of fold *)vallwt_fold:('b->'a->'bLwt.t)->'b->'at->'bLwt.t(** lwt version of fold_back *)vallwt_fold_back:('b->'a->'bLwt.t)->'b->'at->'bLwt.t(** Move a node from one dlist to another one, without finalizing.
If one value is removed from the destination list (because its
maximum size is reached), it is returned (after finalisation). *)valmove:'anode->'at->'aoption(** change the maximum size ;
returns the list of removed values, if any. *)valset_maxsize:'at->int->'alist(** record a function to be called automatically on a piece of data
just before it disappears from the list
(either by explicit removal or because the maximum size is exceeded) *)valadd_finaliser_before:('anode->unit)->'at->unit(** replace all finalizers by a new one. Be very careful while using this. *)valset_finaliser_before:('anode->unit)->'at->unit(** returns the finalizers. *)valget_finaliser_before:'at->('anode->unit)(** record a function to be called automatically on a piece of data
just after it disappears from the list
(either by explicit removal or because the maximum size is exceeded) *)valadd_finaliser_after:('anode->unit)->'at->unit(** replace all finalizers by a new one. Be very careful while using this. *)valset_finaliser_after:('anode->unit)->'at->unit(** returns the finalizers. *)valget_finaliser_after:'at->('anode->unit)end=structtype'anode={mutablevalue:'a;mutablesucc:'anodeoption;(* the node added just after *)mutableprev:'anodeoption;(* the node added just before *)mutablemylist:'atoption;(* the list to which it belongs *)mutablecollection:floatoption;(* the timestamp for removal *)}(* Doubly-linked list with maximum size.
The field [oldest] is the first
element that must be removed if the list becomes too long.
*)and'at={mutablenewest:'anodeoption(* None = empty *);mutableoldest:'anodeoption;mutablesize:int;mutablemaxsize:int;mutablefinaliser_before:'anode->unit;mutablefinaliser_after:'anode->unit;(* *)time_bound:time_boundoption;}andtime_bound={(* *)timer:float;mutablecollector:unitLwt.toption;}(* Checks (by BY):
let compute_length c =
let rec aux i = function
| Some {prev=p} -> aux (i + 1) p
| None -> i
in aux 0 c.newest
let correct_node n =
(match n.succ with
| None -> true
| Some n' -> n'.prev == Some n) &&
(match n.prev with
| None -> true
| Some n' -> n'.succ == Some n)
(* Check that a list is correct. To be completed
1. by adding a check on nodes,
2. by verifying that newest can be reached from oldest and respectively *)
let correct_list l =
(l.size <= l.maxsize) &&
(compute_length l = l.size) &&
(match l.oldest with
| None -> true
| Some n -> n.prev = None) &&
(match l.newest with
| None -> true
| Some n -> n.succ = None)
*)letcreate?timersize={newest=None;oldest=None;size=0;maxsize=size;finaliser_before=(fun_->());finaliser_after=(fun_->());time_bound=(matchtimerwith|None->None|Somet->Some{timer=t;collector=None;});}(* Remove an element from its list - don't finalise *)letremove'nodel=(* assertion (node.mylist = Some l' with l' == l); *)letoldest=matchl.oldestwith|Somenwhennode==n->node.succ|_->l.oldestinletnewest=matchl.newestwith|Somenwhennode==n->node.prev|_->l.newestin(matchnode.succwith|None->()|Somes->s.prev<-node.prev);(matchnode.prevwith|None->()|Somes->s.succ<-node.succ);l.oldest<-oldest;l.newest<-newest;node.mylist<-None;l.size<-l.size-1(* Remove an element from its list - and finalise *)letremovenode=matchnode.mylistwith|None->()|Somelasa->(tryl.finaliser_beforenode;assert(node.mylist==a);remove'nodelwithe->remove'nodel;raisee);l.finaliser_afternode(* These next functions are for the collecting thread *)(* computing the timestamp for a node *)letcollect_timer=function|{time_bound=Some{timer=t;_};_}->Some(t+.(Unix.gettimeofday()))|{time_bound=None;_}->None(* do collect. We first check if the node is still in the list and then if
* its collection hasn't been rescheduled ! *)letcollectdln=matchn.mylistwith|Somelwhenl==dl->beginmatchn.collectionwith|None->assertfalse|Somec->ifc<Unix.gettimeofday()thenremovenelse()end|None|Some_->()letsleep_until=function(*/!\ COOPERATES*)|None->assertfalse(* collection is set to None and collector to Some *)|Somet->letduration=t-.Unix.gettimeofday()inifduration<=0.thenLwt.return()elseLwt_unix.sleepduration(* a function to set the collector. *)letrecupdate_collectorr=matchr.time_boundwith|None(* Not time bounded dlist *)|Some{collector=Some_;_}->()(* Already collecting *)|Some({collector=None;_}ast)->matchr.oldestwith|None->()(* Empty dlist *)|Somen->t.collector<-Some(sleep_untiln.collection>>=fun()->collectrn;t.collector<-None;update_collectorr;Lwt.return())(* Add a node that do not belong to any list to a list.
The fields [succ] and [prev] are overridden.
If the list is too long, the function returns the oldest value.
The node added becomes the element [list] of the list *)(* do not finalise *)(* not exported *)letadd_nodenoder=assert(node.mylist=None);node.mylist<-Somer;letres=matchr.newestwith|None->node.succ<-None;node.prev<-None;r.newest<-Somenode;r.oldest<-r.newest;r.size<-1;None|Somerl->node.succ<-None;node.prev<-r.newest;rl.succ<-Somenode;r.newest<-Somenode;r.size<-r.size+1;ifr.size>r.maxsizethenr.oldestelseNoneinnode.collection<-collect_timerr;update_collectorr;resletaddxl=letcreate_onea={value=a;succ=None;prev=None;mylist=None;collection=None;}in(* create_one not exported *)matchadd_node(create_onex)lwith|None->None|Somev->removev;Somev.valueletnewesta=a.newestletoldesta=a.oldestletsizec=c.sizeletmaxsizec=c.maxsizeletget_timerc=matchc.time_boundwith|None->None|Sometb->Sometb.timerletvaluen=n.valueletlist_ofn=n.mylistletupnode=matchnode.mylistwith|None->()|Somel->matchl.newestwith|Somenwhennode==n->()|_->remove'nodel;ignore(add_nodenodel)(* assertion: = None *)(* we must not change the physical address => use add_node *)letrecremove_n_oldestln=(* remove the n oldest values
(or less if the list is not long enough) ;
returns the list of removed values *)ifn<=0then[]elsematchl.oldestwith|None->[]|Somenode->letv=node.valueinremovenode;(* and finalise! *)v::remove_n_oldestl(n-1)(* Move a node from one dlist to another one, without finalizing *)letmovenodel=(matchnode.mylistwith|None->()|Somel->remove'nodel);matchadd_nodenodelwith|None->None|Somev->removev;Somev.value(* fold over the elements from the newest to the oldest *)letlwt_foldfaccu{newest;_}=matchnewestwith|None->Lwt.returnaccu|Somenewest->letrecfoldaccunode=faccunode.value>>=funaccu->matchnode.prevwith|None->Lwt.returnaccu|Somenew_nodewhennew_node==newest->Lwt.returnaccu|Somenew_node->foldaccunew_nodeinfoldaccunewest(* fold over the elements from the oldest to the newest *)letlwt_fold_backfaccu{oldest;_}=matcholdestwith|None->Lwt.returnaccu|Someoldest->letrecfoldaccunode=faccunode.value>>=funaccu->matchnode.succwith|None->Lwt.returnaccu|Somenew_nodewhennew_node==oldest->Lwt.returnaccu|Somenew_node->foldaccunew_nodeinfoldaccuoldest(* fold over the elements from the newest to the oldest *)letfoldfaccu{newest;_}=matchnewestwith|None->accu|Somenewest->letrecfoldaccunode=letaccu=faccunode.valueinmatchnode.prevwith|None->accu|Somenew_nodewhennew_node==newest->accu|Somenew_node->foldaccunew_nodeinfoldaccunewest(* fold over the elements from the oldest to the newest *)letfold_backfaccu{oldest;_}=matcholdestwith|None->accu|Someoldest->letrecfoldaccunode=letaccu=faccunode.valueinmatchnode.succwith|None->accu|Somenew_nodewhennew_node==oldest->accu|Somenew_node->foldaccunew_nodeinfoldaccuoldestletset_maxsizelm=letsize=l.sizeinifm>=sizethen(l.maxsize<-m;[])elseifm<=0thenfailwith"Dlist.set_maxsize"elseletll=remove_n_oldestl(size-m)inl.maxsize<-m;llletset_finaliser_beforefl=l.finaliser_before<-fletget_finaliser_beforel=l.finaliser_beforeletadd_finaliser_beforefl=letoldf=l.finaliser_beforeinl.finaliser_before<-(funn->oldfn;fn)letset_finaliser_afterfl=l.finaliser_after<-fletget_finaliser_afterl=l.finaliser_afterletadd_finaliser_afterfl=letoldf=l.finaliser_afterinl.finaliser_after<-(funn->oldfn;fn)endmoduleWeak=Weak.Make(structtypet=unit->unitlethash=Hashtbl.hashletequal=(==)end)letclear_all=Weak.create17moduleMake=functor(A:sigtypekeytypevalueend)->structmoduleH=Hashtbl.Make(structtypet=A.keyletequalaa'=a=a'lethash=Hashtbl.hashend)typet={mutablepointers:A.keyDlist.t;mutabletable:(A.value*A.keyDlist.node)H.t;finder:A.key->A.valueLwt.t;clear:unit->unit(* This function clears the cache. It is put inside the
cache structure so that it is garbage-collected only when the cache
is no longer referenced, as the functions themselves are put inside
a weak hash table *)}letmk?timersize=let(l,t)asa=(Dlist.create?timersize,H.createsize)inDlist.set_finaliser_after(funn->H.removet(Dlist.valuen))l;aletreccreatef?timersize=letreccache=let(l,t)=mk?timersizein{pointers=l;table=t;finder=f;clear=f_clear;}andf_clear=(fun()->clearcache)inWeak.addclear_allf_clear;cacheandclearcache=letsize=Dlist.maxsizecache.pointersinlettimer=Dlist.get_timercache.pointersinlet(l,t)=mk?timersizeincache.pointers<-l;cache.table<-t(* not exported *)letpokecachenode=assert(matchDlist.list_ofnodewith|None->false|Somel->cache.pointers==l);Dlist.upnodeletfind_in_cachecachek=let(v,node)=H.findcache.tablekinpokecachenode;vletremovecachek=trylet(_v,node)=H.findcache.tablekinassert(matchDlist.list_ofnodewith|None->false|Somel->cache.pointers==l);Dlist.removenodewithNot_found->()(* Add in a cache, under the hypothesis that the value is
not already in the cache *)letadd_no_removecachekv=ignore(Dlist.addkcache.pointers);matchDlist.newestcache.pointerswith|None->assertfalse|Somen->H.addcache.tablek(v,n)letaddcachekv=removecachek;add_no_removecachekvletsizec=Dlist.sizec.pointersletfindcachek=(tryLwt.return(find_in_cachecachek)withNot_found->cache.finderk>>=funr->(try(* it may have been added during cache.finder *)ignore(find_in_cachecachek)withNot_found->add_no_removecachekr);Lwt.returnr)classcachef?timersize_c=letc=createf?timersize_cinobjectmethodclear()=clearcmethodfind=findcmethodadd=addcmethodsize=sizecmethodfind_in_cache=find_in_cachecmethodremove=removecendendletclear_all_caches()=Weak.iter(funf->f())clear_all