Questo è un esempio di un webserver incapsulato dentro un demone. Non è specifico per LazWeb.
L'idea è quella di avere 3 attori: il demone, il webserver (con tutti i suoi bei thread e le sue logiche e che abbia un metodo per essere attivato e un metodo per disattivarsi) e un thread di controllo che farà da ponte tra il demone e il webserver.
Devi orchestrare un po' di comunicazioni tra tutti e 3.
Il demone è comandato chiaramente da fuori. Possiede un TSimpleEvent che servirà da callback tra lui e il thread di controllo durante la fase di spegnimento. L'istanza dell'evento dovrà essere passata dal demone al costruttore del thread di controllo.
Il thread di controllo si salverà il puntatore all'evento/callback del demone e creerà un altro evento (che deve essere esposto) per funzionare da start/stop del thread stesso. Il thread di controllo creerà anche il webserver vero e proprio.
Quando il thread di controllo partirà, farà partire anche il webservice e poi si metterà in attesa indefinitamente sull'evento interno.
Se il demone dovesse ricevere da fuori (l'utente) il comando di spegnersi, attiverà l'evento del thread di controllo (per svegliarlo) e poi si metterà in attesa sull'evento/callback (in attesa che il thread di controllo abbia ucciso il webserver). Il thread di controllo appena risvegliato chiederà al webserver di chiudersi e poi attiverà l'evento/callback del demone per comunicare che lui ha finito.
A questo punto il demone dovrebbe riprendere la propria esecuzione e spegnersi definitivamente.
TWebServiceDaemon = class(TCustomDaemon)
private
FDaemonWorkerThread : TDaemonWorkerThread;
FDieDaemon : TSimpleEvent;
procedure ThreadStopped (Sender : TObject);
public
function Start : Boolean; override;
function Stop : Boolean; override;
Function ShutDown : Boolean; override;
end;
procedure TWebServiceDaemon.ThreadStopped(Sender: TObject);
begin
FreeAndNil(FDaemonWorkerThread);
FreeAndNil(FDieDaemon);
end;
function TWebServiceDaemon.Start: Boolean;
begin
Result:=inherited Start;
// Service started
FDieDaemon := TSimpleEvent.Create;
FDaemonWorkerThread := TDaemonWorkerThread.Create(FDieDaemon);
FDaemonWorkerThread.OnTerminate:=@ThreadStopped;
FDaemonWorkerThread.FreeOnTerminate:=False;
FDaemonWorkerThread.Start;
end;
function TWebServiceDaemon.Stop: Boolean;
begin
Result:=inherited Stop;
//Service stopped
FDaemonWorkerThread.LetsGoEvent.SetEvent;
FDieDaemon.ResetEvent;
FDaemonWorkerThread.Terminate;
FDieDaemon.WaitFor(5000);
end;
function TWebServiceDaemon.ShutDown: Boolean;
begin
Result:= Stop;
end;
questo lato demone, lato worker/server http:
TDaemonWorkerThread = class (TThread)
private
FServer: TIlmioserverHTTPConLaSuaBellaImplementazione;
FLetsGoEvent : TSimpleEvent;
FLetsDieEvent : TSimpleEvent;
protected
procedure Execute; override;
public
constructor Create (aLetsDieEvent : TSimpleEvent); reintroduce;
destructor Destroy; override;
property LetsGoEvent : TSimpleEvent read FLetsGoEvent;
end;
constructor TDaemonWorkerThread.Create(aLetsDieEvent : TSimpleEvent);
begin
inherited Create(false);
FLetsGoEvent := TSimpleEvent.Create;
FLetsGoEvent.ResetEvent;
FLetsDieEvent := aLetsDieEvent;
FServer := TIlmioserverHTTPConLaSuaBellaImplementazione.Create(...);
FServer.InizializzoQuelloCheServe;
end;
destructor TDaemonWorkerThread.Destroy;
begin
FLetsGoEvent.Free;
FreeAndNil(FServer);
inherited Destroy;
end;
procedure TDaemonWorkerThread.Execute;
begin
// Daemon worker thread executing
while not Terminated do
begin
if not FServer.Attivo then
begin
FServer.VoglioCheTiAttivi;
// Server running
end;
if not Terminated then
begin
// Daemon worker thread waiting...
FLetsGoEvent.WaitFor(INFINITE);
FLetsGoEvent.ResetEvent;
// Daemon worker thread resumed!
end;
end;
if FServer.SeiAttivo? then
begin
FServer.VoglioCheTiChiudi;
// server closed
end;
Sleep (500); // questo alle volte aiuta se il server impiega un po' a tirarsi giù.... ma devi valutare tu, magari puoi passare una callback al server...
FLetsDieEvent.SetEvent;
// Daemon worker thread terminated
end;
@Mimmo
Un consiglio per la parte di codice che indico qui sotto:
function TWebServiceDaemon.Stop: Boolean;
begin
(* Originale
Result:=inherited Stop;
//Service stopped
FDaemonWorkerThread.LetsGoEvent.SetEvent;
FDieDaemon.ResetEvent;
FDaemonWorkerThread.Terminate;
FDieDaemon.WaitFor(5000);
*)
//Service stopped
//Prima imposta il TERMINATE al THREAD
FDaemonWorkerThread.Terminate;
//Poi SETTA l'evento come hai fatto (visto che il THREAD ha un Wait all'infinito)
//Se lo fai con la sequenza che avevi impostato originarimente, il TERMINATE poteva "arrivare" in ritardo e teoricamente il thread poteva ritornare ad attendere all'infinito
//(in fondo hai uno sleep, quindi l'ipotesi è molto rara ... però).
//in questo modo, come sente l'evento sei certo che il THREAD è già in stato di Terminate e si chiuderà dopo avere svolto il suo codice.
FDaemonWorkerThread.LetsGoEvent.SetEvent;
FDieDaemon.ResetEvent;
//Perchè attendi questo ?
//FDieDaemon.WaitFor(5000);
//Attendi invece direttamente la fine del Working Thread
FDaemonWorkerThread.WaitFor(5000);
//Normalmente questa è l'ultima istruzione, però magari in Linux deve essere effettuata prima.
Result := inherited Stop;
end;
@Mimmo
Un consiglio per la parte di codice che indico qui sotto:
Ciao,
grazie per le ottimizzazioni. Hai assolutamente ragione sul fatto che il Terminate è meglio metterlo prima. A questo punto anche ResetEvent è meglio anticiparlo. Invece il WaitFor sul thread lo eviterei perchè non c'è la possibilità di mettergli un timeout (o almeno non so come fare...), nel codice fpc del TThread fa un wait su un tempo "INFINITE" e non prende parametri. E' difficile valutare se c'è un rischio che poi rimanga tutto appeso in chiusura del demone/servizio. Il Result possiamo pure buttarlo a True alla fine, tanto l'inherited non ha logica dentro..
Alla luce delle osservazioni, rimescolando di nuovo il codice, alla fine farei così:
function TWebServiceDaemon.Stop: Boolean;
begin
FDaemonWorkerThread.Terminate;
FDieDaemon.ResetEvent;
FDaemonWorkerThread.LetsGoEvent.SetEvent;
FDieDaemon.WaitFor(5000);
Result:=true;
end;
Che ne dici?