123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331(* Ocsigen
* http://www.ocsigen.org
* Module eliommod_gc.ml
* Copyright (C) 2007 Vincent Balat
*
* 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.
*)(** Garbage collection of services and session data *)letsection=Lwt_log.Section.make"eliom:gc"openEliom_libopenLwt(*****************************************************************************)letservicesessiongcfrequency=ref(Some1200.)(* 20 min ? *)letdatasessiongcfrequency=ref(Some1200.)letpersistentsessiongcfrequency=ref(Some86400.)letset_servicesessiongcfrequencyi=servicesessiongcfrequency:=iletset_datasessiongcfrequencyi=datasessiongcfrequency:=iletget_servicesessiongcfrequency()=!servicesessiongcfrequencyletget_datasessiongcfrequency()=!datasessiongcfrequencyletset_persistentsessiongcfrequencyi=persistentsessiongcfrequency:=iletget_persistentsessiongcfrequency()=!persistentsessiongcfrequency(* garbage collection of timeouted sessions *)letgc_timeouted_servicesnowtables=letrecauxtfilenamedireltrthr=thr>>=fun()->(* we wait for the previous one to be completed *)match!direltrwith|Eliom_common.Dirr->empty_oner>>=fun()->(match!rwith|Eliom_common.Vide->(match!twith|Eliom_common.Vide->()|Eliom_common.Tabletr->letnewr=String.Table.removefilenametrinifString.Table.is_emptynewrthent:=Eliom_common.Videelset:=Eliom_common.Tablenewr)|_->());Lwt.return_unit|Eliom_common.Fileptr->Eliom_common.Serv_Table.fold(*VVV not tail recursive: may be a problem if lots of coservices *)(funptk(`Ptc(nodeopt,l))thr->let%lwt_=thrin(* we wait for the previous one to be completed *)(matchnodeopt,lwith|Somenode,{Eliom_common.s_expire=Some(_,e);_}::_(* it is an anonymous coservice. The list should
have length 1 here *)when!e<now->Ocsigen_cache.Dlist.removenode|Somenode,[](* should not occur *)->Ocsigen_cache.Dlist.removenode|_->((* We find the data associated to ptk once again,
because it may have changed, then we update it
(without cooperation)
(it's ok because the list is probably not large) *)trylet`Ptc(nodeopt,l),ll=(Eliom_common.Serv_Table.findptk!ptr,Eliom_common.Serv_Table.removeptk!ptr)inifnodeopt=NonethenmatchList.fold_right(fun({Eliom_common.s_expire;_}asa)foll->matchs_expirewith|Some(_,e)when!e<now->foll|_->a::foll)l[]with|[]->ptr:=ll|newl->ptr:=Eliom_common.Serv_Table.addptk(`Ptc(nodeopt,newl))llwithNot_found->()));Lwt.pause())!ptrreturn_unit>>=fun()->(ifEliom_common.Serv_Table.is_empty!ptrthenmatch!twith|Eliom_common.Vide->()|Eliom_common.Tabletr->letnewr=String.Table.removefilenametrinifString.Table.is_emptynewrthent:=Eliom_common.Videelset:=Eliom_common.Tablenewr);Lwt.return_unitandempty_onet=match!twith|Eliom_common.Vide->Lwt.return_unit|Eliom_common.Tabler->(ifString.Table.is_emptyrthen(t:=Eliom_common.Vide;Lwt.return_unit)elseString.Table.fold(auxt)rLwt.return_unit>>=fun()->match!twith(* !t has probably changed *)|Eliom_common.Vide->Lwt.return_unit|Eliom_common.Tabler->ifString.Table.is_emptyrthent:=Eliom_common.Vide;Lwt.return_unit)inLwt_list.iter_s(fun(_,_prio,t)->empty_onet)tables.Eliom_common.table_services>>=fun()->tables.Eliom_common.table_services<-List.filter(funr->!(Tuple3.thdr)<>Eliom_common.Vide)tables.Eliom_common.table_services;Lwt.return_unitletgc_timeouted_naservicesnowtr=match!trwith|Eliom_common.AVide->return_unit|Eliom_common.ATablet->ifEliom_common.NAserv_Table.is_emptytthen(tr:=Eliom_common.AVide;Lwt.return_unit)elseEliom_common.NAserv_Table.fold(funk(_,_,expdate,_,nodeopt)thr->thr>>=fun()->(matchexpdatewith|Some(_,e)when!e<now->(matchnodeoptwith|Somenode->Ocsigen_cache.Dlist.removenode(* will remove from the table automatically *)|_->tr:=Eliom_common.remove_naservice_table!trk)|_->());Lwt.pause())treturn_unit(* This is a thread that will work for example every hour. *)letservice_session_gcsitedata=lettables=sitedata.Eliom_common.global_servicesinmatchget_servicesessiongcfrequency()with|None->()(* No garbage collection *)|Somet->letrecf()=Lwt_unix.sleept>>=fun()->letservice_cookie_table=sitedata.Eliom_common.session_servicesinletnow=Unix.time()inLwt_log.ign_info~section"GC of service sessions";(* public continuation tables: *)(iftables.Eliom_common.table_contains_services_with_timeoutthengc_timeouted_servicesnowtableselsereturn_unit)>>=fun()->(iftables.Eliom_common.table_contains_naservices_with_timeoutthengc_timeouted_naservicesnowtables.Eliom_common.table_naserviceselsereturn_unit)>>=fun()->(* private continuation tables: *)Eliom_common.SessionCookies.fold(funk{Eliom_common.Service_cookie.session_table=tables;expiry;session_group;session_group_node;_}thr->thr>>=fun()->(match!expirywith|Someexpwhenexp<now->Eliommod_sessiongroups.Serv.removesession_group_node;Lwt.return_unit|_->(iftables.Eliom_common.table_contains_services_with_timeoutthengc_timeouted_servicesnowtableselsereturn_unit)>>=fun()->(iftables.Eliom_common.table_contains_naservices_with_timeoutthengc_timeouted_naservicesnowtables.Eliom_common.table_naserviceselsereturn_unit)>>=fun()->(match!session_groupwith|_,_scope,Right_(* no group *)(*VVV check this *)whenEliommod_sessiongroups.Serv.group_size(Eliom_common.get_site_dir_stringsitedata,`Client_process,Leftk)=0(* no tab sessions *)&&Eliom_common.service_tables_are_emptytables->(* The session is not used in any table
and is not in a group
(scope must be `Session,
as all tab sessions are in a group),
and is not associated to any tab session.
We can remove it. *)Eliommod_sessiongroups.Serv.removesession_group_node|_->()(*VVV enough? *));return_unit)>>=Lwt.pause)service_cookie_tablereturn_unit>>=finLwt.asyncf(* This is a thread that will work for example every hour. *)letdata_session_gcsitedata=matchget_datasessiongcfrequency()with|None->()(* No garbage collection *)|Somet->letrecf()=Lwt_unix.sleept>>=fun()->letdata_cookie_table=sitedata.Eliom_common.session_datainletnot_bound_in_data_tables=sitedata.Eliom_common.not_bound_in_data_tablesinletnow=Unix.time()inLwt_log.ign_info~section"GC of session data";(* private continuation tables: *)Eliom_common.SessionCookies.fold(funk{Eliom_common.Data_cookie.expiry;session_group;session_group_node;_}thr->thr>>=fun()->(match!expirywith|Someexpwhenexp<now->Eliommod_sessiongroups.Data.removesession_group_node;return_unit|_->(match!session_groupwith|_,scope,Right_(* no group *)whenEliommod_sessiongroups.Data.group_size(Eliom_common.get_site_dir_stringsitedata,`Client_process,Leftk)=0(* no tab sessions *)&¬_bound_in_data_tablesk->(* The session is not used in any table
and is not in a group
(scope must be `Session,
as all tab sessions are in a group),
and is not associated to any tab session.
We can remove it. *)ifscope<>`SessionthenLwt_log.ign_error~section"Eliom: Group associated to IP has scope different from `Session. Please report the problem.";Eliommod_sessiongroups.Data.removesession_group_node;(* See also the finalisers in Eliommod_sessiongroups
and Eliommod.ml *)Lwt.return_unit|_->Lwt.return_unit))>>=Lwt.pause)data_cookie_tablereturn_unit>>=finLwt.asyncf(* garbage collection of timeouted persistent sessions *)(* This is a thread that will work every hour/day *)letpersistent_session_gcsitedata=letgc()=letnow=Unix.time()inletlog_hashc=Eliom_common.Hashed_cookies.(sha256c)inletdo_gc_cookiecookie{Eliommod_cookies.full_state_name;expiry;session_group;_}=letscope=full_state_name.Eliom_common.user_scopeinmatchexpirywith|Someexpwhenexp<=now->Lwt_log.ign_info_f~section"remove expired cookie %s"(log_hashcookie);Eliommod_persess.close_persistent_state2~scopesitedatasession_groupcookie(*WAS: remove_from_all_persistent_tables k *)|_->Lwt_log.ign_info_f~section"cookie not expired: %s"(log_hashcookie);return_unitinletgc_cookiec=Lwt.try_bind(fun()->Eliommod_cookies.Persistent_cookies.Cookies.findc)(do_gc_cookiec)(function|Not_found->Lwt_log.ign_info_f~section"cookie does not exist: %s"(log_hashc);Lwt.return_unit|exn->Lwt.failexn)inLwt_log.ign_info~section"GC of persistent sessions";Eliommod_cookies.Persistent_cookies.garbage_collect~sectiongc_cookieinmatchget_persistentsessiongcfrequency()with|None->()(* No garbage collection *)|Somet->letrecf()=Lwt_unix.sleept>>=gc>>=finLwt.asyncf