123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239(* This file is free software, part of dolmen. See file "LICENSE" for more information *)exceptionSigintexceptionOut_of_timeexceptionOut_of_spacemoduleMake(State:State.S)=struct(* GC alarm for time/space limits *)(* ************************************************************************ *)(* This function analyze the current size of the heap
TODO: take into account the minor heap size
TODO: should we only consider the live words ? *)letchecksize_limit=function()->letheap_size=(Gc.quick_stat()).Gc.heap_wordsinlets=floatheap_size*.floatSys.word_size/.8.inifs>size_limitthenraiseOut_of_space(* There are two kinds of limits we want to enforce:
- a size limit: we use the Gc's alarm function to enforce the limit
on the size of the RAM used
- a time limit: the Gc alarm is not reliable to enforce this, so instead
we use the Unix.timer facilities
TODO: this does not work on windows.
TODO: allow to use the time limit only for some passes *)letsetup_alarmts=ift<>infinitythenignore(Unix.setitimerUnix.ITIMER_REALUnix.{it_value=t;it_interval=0.01});ifs<>infinitythen(Some(Gc.create_alarm(checks)))elseNoneletdelete_alarmalarm=(* it's alwyas safe to delete the timer here,
even if none was present before. *)ignore(Unix.setitimerUnix.ITIMER_REALUnix.{it_value=0.;it_interval=0.});matchalarmwithNone->()|Somealarm->Gc.delete_alarmalarm(* The Unix.timer works by sending a Sys.sigalrm, so in order to use it,
we catch it and raise the Out_of_time exception. *)let()=Sys.set_signalSys.sigalrm(Sys.Signal_handle(fun_->raiseOut_of_time))(* We also want to catch user interruptions *)let()=Sys.set_signalSys.sigint(Sys.Signal_handle(fun_->raiseSigint))(* Pipeline and execution *)(* ************************************************************************ *)type'stmerge='st->'st->'sttype('a,'b)cont=[`Doneof'a|`Continueof'b]type('st,'a)fix=[`Ok|`Genof'stmerge*('st->'st*'aoption)]type'stk_exn={k:'a.'st->Printexc.raw_backtrace->exn->'a;}type('st,'a,'b)op={name:string;f:'st->'a->'st*'b;}(* Type for pipelines, i.e a series of transformations to
apply to the input. An ('st, 'a, 'b) t is a pipeline that
takes an input of type ['a] and returns a value of type
['b]. *)type(_,_,_)t=(* The end of the pipeline, the identity/reflexive constructor *)|End:('st,'a,'a)t(* Apply a single function and then proceed with the rest of the pipeline *)|Map:('st,'a,'c)op*('st,'c,'b)t->('st,'a,'b)t(* Allow early exiting from the loop *)|Cont:('st,'a,('b,'c)cont)op*('st,'c,'b)t->('st,'a,'b)t(* Concat two pipeline. Not tail recursive. *)|Concat:('st,'a,'b)t*('st,'b,'c)t->('st,'a,'c)t(* Fixpoint expansion *)|Fix:('st,'a,('st,'a)fix)op*('st,'a,unit)t->('st,'a,unit)t(* Creating operators. *)letop?(name="")f={name;f;}letapply?namef=op?name(funstx->st,fx)letiter_?namef=op?name(funstx->fx;st,x)letf_map?name?(test=(fun__->true))f=op?name(funstx->ifteststxthenbeginletst',y=fstxinst',`Continueyendelsest,`Donex)(* Creating pipelines. *)let_end=Endlet(@>>>)opt=Map(op,t)let(@>|>)opt=Cont(op,t)let(@|||)tt'=Concat(t,t')letfixopt=Fix(op,t)(* Eval an operator *)leteval_op~exnopstx=tryop.fstxwithe->letbt=Printexc.get_raw_backtrace()inexn.kstbte(* Eval a pipeline into the corresponding function *)letreceval:typestab.exn:stk_exn->(st,a,b)t->st->a->st*b=fun~exnpipestx->matchpipewith|End->st,x|Map(op,t)->letst',y=eval_op~exnopstxineval~exntst'y|Cont(op,t)->letst',y=eval_op~exnopstxinbeginmatchywith|`Continueres->eval~exntst'res|`Doneres->st',resend|Concat(t,t')->letst',y=eval~exntstxineval~exnt'st'y|Fix(op,t)->letst',y=eval_op~exnopstxinbeginmatchywith|`Ok->eval~exntst'x|`Gen(merge,g)->letst''=eval_gen_fold~exnpipest'ginletst'''=mergestst''inst''',()endandeval_gen_fold:typesta.exn:stk_exn->(st,a,unit)t->st->(st->st*aoption)->st=fun~exnpipestg->matchgstwith|st,None->st|st,Somex->letst',()=eval~exnpipestxineval_gen_fold~exnpipest'g|exceptione->letbt=Printexc.get_raw_backtrace()inexn.kstbte(* Aux function to eval a pipeline on the current value of a generator. *)letrun_aux~exnpipegst=matchgstwith|st,None->`Donest|st,Somex->`Continue(eval~exnpipestx)|exceptione->letbt=Printexc.get_raw_backtrace()inexn.kstbte(* Effectively run a pipeline on all values that come from a generator.
Time/size limits apply for the complete evaluation of each input
(so all expanded values count toward the same limit). *)letrecrun:typea.finally:(State.t->(Printexc.raw_backtrace*exn)option->State.t)->(State.t->State.t*aoption)->State.t->(State.t,a,unit)t->State.t=fun~finallygstpipe->letexceptionExnofState.t*Printexc.raw_backtrace*exninlettime=State.getState.time_limitstinletsize=State.getState.size_limitstinletal=setup_alarmtimesizeinletexn={k=funstbte->(* delete alarm as soon as possible *)let()=delete_alarmalin(* go the the correct handler *)raise(Exn(st,bt,e));}inbeginmatchrun_aux~exnpipegstwith(* End of the run, yay ! *)|`Donest->let()=delete_alarmalinst(* Regular case, we finished running the pipeline on one input
value, let's get to the next one. *)|`Continue(st',())->let()=delete_alarmalinletst''=tryfinallyst'Nonewith_->st'inrun~finallygst''pipe(* "Normal" exception case: the exn was raised by an operator, and caught
then re-raised by the {exn} cotinuation passed to run_aux *)|exceptionExn(st,bt,e)->(* delete alarm *)let()=delete_alarmalin(* Flush stdout and print a newline in case the exn was
raised in the middle of printing *)Format.pp_print_flushFormat.std_formatter();Format.pp_print_flushFormat.err_formatter();(* Go on running the rest of the pipeline. *)letst'=finallyst(Some(bt,e))inrun~finallygst'pipe(* Exception case for exceptions, that can realisically occur for all
asynchronous exceptions, or if some operator was not properly wrapped.
In this error case, we might use a rather old and outdate state, but
this should not happen often, and should not matter for asynchronous
exceptions. *)|exceptione->letbt=Printexc.get_raw_backtrace()in(* delete alarm *)let()=delete_alarmalin(* Flush stdout and print a newline in case the exn was
raised in the middle of printing *)Format.pp_print_flushFormat.std_formatter();Format.pp_print_flushFormat.err_formatter();(* Go on running the rest of the pipeline. *)letst'=finallyst(Some(bt,e))inrun~finallygst'pipeendend