123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2023 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* 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, version 2.1. *)(* *)(* It 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. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)(* -------------------------------------------------------------------------- *)(* --- Server Main Process --- *)(* -------------------------------------------------------------------------- *)moduleSenv=Server_parametersmoduleSignals=Set.Make(String)(* -------------------------------------------------------------------------- *)(* --- Registry --- *)(* -------------------------------------------------------------------------- *)typekind=[`GET|`SET|`EXEC]letstring_of_kind=function`GET->"GET"|`SET->"SET"|`EXEC->"EXEC"letpp_kindfmtkd=Format.pp_print_stringfmt(string_of_kindkd)letregistry=Hashtbl.create32letregister(kind:kind)requesthandler=ifHashtbl.memregistryrequestthenServer_parameters.failure"Request '%s' already registered"requestelseHashtbl.addregistryrequest(kind,handler)letfindrequest=trySome(Hashtbl.findregistryrequest)withNot_found->Noneletexecrequestdata=(snd(Hashtbl.findregistryrequest))data(* -------------------------------------------------------------------------- *)(* --- Public API --- *)(* -------------------------------------------------------------------------- *)typejson=Json.ttype'arequest=[|`Poll|`Requestof'a*string*json|`Killof'a|`SigOnofstring|`SigOffofstring|`Shutdown]type'aresponse=[|`Dataof'a*json|`Errorof'a*string|`Killedof'a|`Signalofstring|`Rejectedof'a|`CmdLineOn|`CmdLineOff]type'amessage={requests:'arequestlist;callback:'aresponselist->unit;}(* Private API: *)type'aprocess={id:'a;request:string;data:json;handler:json->json;yield:bool;mutablekilled:bool;}type'apending=|Processof'aprocess|Commandof(unit->unit)type'arunning=|Idle(* Server is waiting for requests *)|CmdLine(* Frama-C command line is running *)|ExecRequestof'aprocess(* Running EXEC process *)(* Server with request identifier (RqId) of type ['a] *)type'aserver={pretty:Format.formatter->'a->unit;(* RqId printer *)equal:'a->'a->bool;(* RqId equality *)polling:int;(* server polling, in milliseconds *)fetch:unit->'amessageoption;(* fetch some client message *)q_in:'apendingQueue.t;(* queue of pending `EXEC and `GET jobs *)q_out:'aresponseQueue.t;(* queue of pending responses *)mutabledaemon:Db.daemonoption;(* Db.yield daemon *)mutables_listen:Signals.t;(* signals the client is listening to *)mutables_emitted:Signals.t;(* emitted signals enqueued *)mutables_pending:Signals.t;(* emitted signals not enqueued yet *)mutableshutdown:bool;(* server has been asked to shut down *)mutablerunning:'arunning;(* server running state *)mutablecmdline:booloption;(* cmdline signal management *)}exceptionKilled(* -------------------------------------------------------------------------- *)(* --- Debug --- *)(* -------------------------------------------------------------------------- *)letpp_requestppfmt(r:_request)=matchrwith|`Poll->Format.fprintffmt"Poll"|`Shutdown->Format.fprintffmt"Shutdown"|`SigOnsg->Format.fprintffmt"Signal %S : on"sg|`SigOffsg->Format.fprintffmt"Signal %S : off"sg|`Killid->Format.fprintffmt"Kill [%a]"ppid|`Request(id,request,data)->ifSenv.debug_atleast2thenFormat.fprintffmt"@[<hov 2>Request [%a] %s@ %a@]"ppidrequestData.prettydataelseFormat.fprintffmt"Request [%a] %s"ppidrequestletpp_processppfmt(p:_process)=Format.fprintffmt"Execute %s:%a"p.requestppp.idletpp_responseppfmt(r:_response)=matchrwith|`Error(id,err)->Format.fprintffmt"Error %a: %s"ppiderr|`Rejectedid->Format.fprintffmt"Rejected %a"ppid|`Killedid->Format.fprintffmt"Killed %a"ppid|`Signalsg->Format.fprintffmt"Signal %S"sg|`CmdLineOn->Format.pp_print_stringfmt"CmdLine On"|`CmdLineOff->Format.pp_print_stringfmt"CmdLine Off"|`Data(id,data)->ifSenv.debug_atleast3thenFormat.fprintffmt"@[<hov 2>Response %a:@ %a@]"ppidData.prettydataelseFormat.fprintffmt"Replied %a"ppidletpp_runningppfmt=function|Idle->Format.pp_print_stringfmt"Idle"|CmdLine->Format.pp_print_stringfmt"CmdLine"|ExecRequest{id}->Format.fprintffmt"ExectRequest [%a]"ppid(* -------------------------------------------------------------------------- *)(* --- Request Handling --- *)(* -------------------------------------------------------------------------- *)letrunproc:_response=tryletdata=proc.handlerproc.datain`Data(proc.id,data)with|Killed->`Killedproc.id|Data.InputErrormsg->`Error(proc.id,msg)|Sys.Breakasexn->raiseexn(* Silently pass the exception *)|exnwhenCmdline.catch_at_toplevelexn->Senv.warning"[%s] Uncaught exception:@\n%s"proc.request(Cmdline.protectexn);`Error(proc.id,Printexc.to_stringexn)letdelayedprocess=ifSenv.debug_atleast1thenSome(fund->Senv.debug"No yield since %dms during %s"dprocess)elseNoneletexecute_processserver?yieldproc=Senv.debug~level:2"%a"(pp_processserver.pretty)proc;letresp=matchyieldwith|Someyieldwhenproc.yield->Db.with_progress~debounced:server.polling?on_delayed:(delayedproc.request)yieldrunproc|_->runprocinQueue.pushrespserver.q_out(* -------------------------------------------------------------------------- *)(* --- Signals --- *)(* -------------------------------------------------------------------------- *)typesignal=stringletsignal_names=sletsignals=Hashtbl.create32letsignals=ifHashtbl.memsignalssthen(Server_parameters.failure"Signal '%s' already registered"s;"")else(Hashtbl.addsignalss[];s)let()=Hashtbl.addsignals""[]leton_signalscallback=letds=Hashtbl.findsignalssinHashtbl.replacesignalss(callback::ds)letnotifysa=matchHashtbl.findsignalsswith|ds->List.iter(funf->fa)ds|exceptionNot_found->()letnop_s=()letemitter=refnopletemits=!emitters(* -------------------------------------------------------------------------- *)(* --- Processing Requests --- *)(* -------------------------------------------------------------------------- *)letraise_if_killed=function|Idle->()|CmdLine->()|ExecRequest{killed}->ifkilledthenraiseKilledletkill_running?ids=matchs.runningwith|Idle->()|CmdLine->ifid=NonethenDb.cancel()|ExecRequestp->matchidwith|None->p.killed<-true|Someid->ifs.equalidp.idthenp.killed<-trueletkill_requesteqid=function|Processp->ifeqidp.idthenp.killed<-true|Command_->()letprocess_request(server:'aserver)(request:'arequest):unit=ifSenv.debug_atleast1&&(Senv.debug_atleast2||request<>`Poll)thenSenv.debug"%a"(pp_requestserver.pretty)request;matchrequestwith|`Poll->()|`Shutdown->beginkill_runningserver;Queue.clearserver.q_in;Queue.clearserver.q_out;server.shutdown<-true;end|`SigOnsg->beginserver.s_listen<-Signals.addsgserver.s_listen;ifSignals.memsgserver.s_pendingthenbeginserver.s_emitted<-Signals.addsgserver.s_emitted;server.s_pending<-Signals.removesgserver.s_pending;Queue.push(`Signalsg)server.q_out;end;notifysgtrue;end|`SigOffsg->beginserver.s_listen<-Signals.removesgserver.s_listen;notifysgfalse;end|`Killid->beginkill_running~idserver;letset_killed=kill_requestserver.equalidinQueue.iterset_killedserver.q_in;end|`Request(id,request,data)->beginmatchfindrequestwith|None->Senv.debug"Rejected %a"server.prettyid;Queue.push(`Rejectedid)server.q_out|Some(kind,handler)->letyield=matchkindwith`GET|`SET->false|`EXEC->trueinletproc={id;request;handler;data;yield;killed=false;}inmatchkindwith|`GET->execute_processserverproc|`SET|`EXEC->Queue.push(Processproc)server.q_inend(* -------------------------------------------------------------------------- *)(* --- Fetching a Bunck of Messages --- *)(* -------------------------------------------------------------------------- *)letcommunicateserver=Senv.debug~level:3"fetch";matchserver.fetch()with|None->false|Somemessage->Senv.debug~level:2"message(s) received";leterror=tryList.iter(process_requestserver)message.requests;Nonewithexn->Someexnin(* re-raised after message reply *)letpool=ref[]inQueue.iter(funr->pool:=r::!pool)server.q_out;Option.iter(funcmd->pool:=(ifcmdthen`CmdLineOnelse`CmdLineOff)::!pool;)server.cmdline;pool:=List.rev!pool;Queue.clearserver.q_out;server.cmdline<-None;server.s_emitted<-Signals.empty;Senv.debug~level:2"response(s) callback";ifSenv.debug_atleast2thenList.iter(Senv.debug"%a"(pp_responseserver.pretty))!pool;message.callback!pool;Option.iterraiseerror;true(* -------------------------------------------------------------------------- *)(* --- Yielding & Signaling --- *)(* -------------------------------------------------------------------------- *)letdo_yieldserver()=raise_if_killedserver.running;ignore(communicateserver)letdo_signalservers=ifSignals.memsserver.s_listenthenbeginifnot(Signals.memsserver.s_emitted)thenbeginserver.s_emitted<-Signals.addsserver.s_emitted;Queue.push(`Signals)server.q_out;endendelseserver.s_pending<-Signals.addsserver.s_pending(* -------------------------------------------------------------------------- *)(* --- One Step Process --- *)(* -------------------------------------------------------------------------- *)letrecprocessserver=ifQueue.is_emptyserver.q_inthencommunicateserverelsematchQueue.popserver.q_inwith|Process{killed}whenkilled->processserver|pending->tryletyield=do_yieldserverinbeginmatchpendingwith|Processp->server.running<-ExecRequestp;execute_processserver~yieldp|Commandcmd->server.running<-CmdLine;Db.with_progress~debounced:server.polling?on_delayed:(delayed"<async>")yieldcmd()end;server.running<-Idle;truewithexn->server.running<-Idle;raiseexn(* -------------------------------------------------------------------------- *)(* --- Server Main Loop --- *)(* -------------------------------------------------------------------------- *)letin_range~min:a~max:bv=min(maxav)bletkill()=raiseKilledletdaemons=ref[]letoncallback=daemons:=!daemons@[callback]letset_activeactivity=List.iter(funf->tryfactivitywith_->())!daemonsletcreate~pretty?(equal=(=))~fetch()=letpolling=in_range~min:1~max:200(Senv.Polling.get())in{fetch;polling;equal;pretty;q_in=Queue.create();q_out=Queue.create();s_listen=Signals.empty;s_emitted=Signals.empty;s_pending=Signals.empty;daemon=None;running=Idle;cmdline=None;shutdown=false;}(* -------------------------------------------------------------------------- *)(* --- Async --- *)(* -------------------------------------------------------------------------- *)letscheduleserverjobdata=letstatus=refTask.Yieldinletcommand()=match!statuswith|Task.Return_->()|_->letst=tryTask.Result(jobdata)with|Db.Cancel->Task.Canceled|exn->Task.Failedexninstatus:=Task.Returnstinletpingcoin=matchcoin,!statuswith|Task.Kill,Task.Yield->letst=Task.ReturnTask.Canceledinstatus:=st;st|_,st->stinQueue.push(Commandcommand)server.q_in;Task.asyncpingtypescheduler=Offline|Server:'aserver->schedulerletscheduler=refOfflineletasyncjobdata=match!schedulerwith|Offline->Task.calljobdata|Servers->schedulesjobdata(* -------------------------------------------------------------------------- *)(* --- Start / Stop --- *)(* -------------------------------------------------------------------------- *)(* public API ; shall be scheduled at command line main stage *)letstartserver=beginSenv.debug~level:2"Server started (was %a)"(pp_runningserver.pretty)server.running;server.running<-CmdLine;server.cmdline<-Sometrue;emitter:=do_signalserver;matchserver.daemonwith|Some_->()|None->beginSenv.feedback"Server enabled.";letdaemon=Db.on_progress~debounced:server.polling?on_delayed:(delayed"command line")(do_yieldserver)inserver.daemon<-Somedaemon;set_activetrue;endend(* public API ; can be invoked to force server shutdown *)letstopserver=beginSenv.debug~level:2"Server stopped (was %a)"(pp_runningserver.pretty)server.running;kill_runningserver;emitter:=nop;matchserver.daemonwith|None->()|Somedaemon->beginSenv.feedback"Server disabled.";server.daemon<-None;server.running<-Idle;server.cmdline<-None;Db.off_progressdaemon;set_activefalse;endend(* internal only ; invoked by run when command line is finished *)letforegroundserver=beginSenv.debug~level:2"Server foreground (was %a)"(pp_runningserver.pretty)server.running;server.running<-Idle;server.cmdline<-Somefalse;emitter:=do_signalserver;Task.on_idle:=Db.while_progress~debounced:50;scheduler:=Serverserver;matchserver.daemonwith|None->()|Somedaemon->beginserver.daemon<-None;Db.off_progressdaemon;endend(* -------------------------------------------------------------------------- *)(* --- Main Loop --- *)(* -------------------------------------------------------------------------- *)(* public API ; shall be invoked at command line normal exit *)letrunserver=try((* TODO: catch-break to be removed once Why3 signal handler is fixed *)Sys.catch_breaktrue);foregroundserver;set_activetrue;Senv.feedback"Server running.";begintrywhilenotserver.shutdowndoletactivity=processserverinifnotactivitythenDb.sleepserver.pollingdone;withSys.Break->()(* Ctr+C, just leave the loop normally *)end;Senv.feedback"Server shutdown.";emitter:=nop;scheduler:=Offline;set_activefalse;withexn->Senv.feedback"Server interruped (fatal error).";emitter:=nop;scheduler:=Offline;set_activefalse;raiseexn(* -------------------------------------------------------------------------- *)