Net-Base Rivista

02.08.2026

Windows Service in Delphi: implementare correttamente l'arresto ordinato con TEvent e timeout di arresto

Quando un servizio Windows si 'blocca' durante lo stop, raramente è casuale: di solito sono thread, I/O o cicli di Sleep senza percorso di uscita a bloccare il processo. Questo contributo pratico mostra come, in Delphi, implementare con TEvent un Graceful Shutdown pulito e gestire correttamente i Stop-Timeouts...

02.08.2026

Dal tema della rivista alla pratica di progetto

Pagine di servizi e tecniche correlate all'articolo

Un Windows Service in Delphi nella pratica quotidiana spesso sembra poco appariscente: gira in background, elabora job, scrive log, parla con database o API REST. Finché qualcuno clicca su „Interrompi servizio“ – o è previsto un reboot per patch – e il servizio non si arresta correttamente. Allora la console dei servizi mostra per minuti „Wird beendet…“, il servizio resta in stato Stop Pending, e nel peggiore dei casi il processo viene terminato forzatamente. Proprio qui conviene trattare il Windows Service in Delphi Graceful Shutdown come un tema architetturale intenzionale: con un segnale di shutdown chiaro, timeout definiti e thread che rispondono realmente.

In questo articolo non si parla di internals di framework, ma di un pattern pratico: TEvent come segnale di stop (un oggetto di sincronizzazione vicino al kernel da System.SyncObjs), combinato con una strategia Stop-Timeout che considera sia il Windows Service Control Manager (SCM, cioè la componente Windows che avvia/interrompe i servizi) sia i propri worker thread. Includiamo casi limite tipici, approcci di debugging e la questione di quando la logica aggiuntiva vale davvero la pena.

Windows Service in Delphi Graceful Shutdown nella pratica

La causa più frequente è semplice: il servizio ha almeno un thread bloccato in un’operazione e non dispone di un percorso di interruzione. Classici:

  • Cicli di polling con Sleep: „while not Terminated do Sleep(1000)“. Allo stop il segnale arriva, ma il thread reagisce solo dopo fino a 1 secondo (o 30 secondi…).
  • I/O bloccante: chiamate al database, richieste HTTP, Named Pipes, attese sul filesystem – tutto ciò che „si limita ad aspettare“ senza considerare un segnale di stop.
  • Consumer della coda senza wakeup: un worker attende su una coda, ma allo stop non viene svegliato per uscire.
  • Ordine dei lock/Deadlock: allo stop si esegue il „cleanup“ mentre altri thread detengono ancora lock. Questo si manifesta spesso solo nel percorso di stop, perché l’ordine lì è diverso rispetto al normale funzionamento.

Lo Windows SCM si aspetta che un servizio reagisca prontamente a un comando di stop e riporti continuamente il proprio stato (tramite SetServiceStatus; Delphi incapsula questo nella componente di servizio). Se accetti l’evento di stop ma non arresti correttamente i tuoi thread, il processo resta vivo – e Windows decide a un certo punto che „ci sta mettendo troppo tempo“. Il risultato è quindi o un arresto forzato o un servizio che resta bloccato in uno stato intermedio poco chiaro.

Principio fondamentale: un segnale di stop che ogni worker comprende

Abstrakte Grafik: Worker-Threads warten auf Work oder Stop-Event und beenden geordnet
Quando i worker attendono „Work o Stop“, la latenza di stop diminuisce senza polling.

Un Graceful Shutdown funziona solo se hai un segnale che:

  • possa essere osservato da tutti i thread rilevanti,
  • esegua un effetto anche da stati di attesa bloccanti,
  • sia nel percorso di Stop deterministico (niente speranze tipo «forse uscirà prima o poi»),
  • abbia una chiara strategia di timeout.

In Delphi TEvent è a questo scopo uno strumento molto utile: un oggetto Event che internamente è realizzato con handle Windows (comparabile a CreateEvent/SetEvent). Puoi usarlo come segnale «Stop requested». Ogni worker allora non attende semplicemente alla cieca, ma aspetta «lavoro o Stop».

Scegliere correttamente TEvent: ManualReset vs. AutoReset

Per i segnali di stop normalmente vuoi Manual Reset (reset manuale): una volta impostato, l’evento resta „signaled“ fino a che non lo resetti. Questo garantisce che ogni thread che entra successivamente in fase di attesa riconosca comunque il segnale di stop. Auto Reset sarebbe rischioso qui, perché resetta automaticamente il segnale dopo un thread in attesa e altri thread potrebbero perdere il segnale di stop.

Delphi-Service-Lebenszyklus: Dove lo Stop arriva realmente

Un Delphi-Windows- und Linux-Services si basa tipicamente su TService (VCL/RTL). Lo SCM invia comandi (Start, Stop, Pause, Continue). Delphi invoca quindi gli eventi/metodi corrispondenti (a seconda del template, ad es. OnStart, OnStop, OnExecute).

Importante per l’architettura:

  • OnStop non è il luogo per attendere a lungo senza aggiornare lo stato. È il punto dove avvii lo shutdown e poi attendi in modo controllato — con timeout.
  • OnExecute è spesso un loop. Se esegui lavoro „infinito“ lì, il loop deve reagire a un segnale di Stop.
  • Worker-Threads (TThread o thread-pool) devono reagire allo stesso segnale di Stop, altrimenti il servizio è logicamente fermo ma fisicamente non ancora terminato.

Schema pulito: Stop-Event + join dei worker + fallback duro

Lo schema pratico consiste di quattro passi:

  1. Richiedere lo Stop: impostare lo Stop-Event, non accettare più nuovi job.
  2. Generare wakeup: se i worker attendono su code o sono in Sleep, devono poter „risvegliarsi“ (es. tramite segnale Event/Queue).
  3. Terminare ordinatamente: i worker escono dai loop, chiudono risorse (connessioni DB, file, handle) e segnalano „completato“.
  4. Timeout e fallback: se non tutto termina nei tempi, devi decidere: continuare ad aspettare (con aggiornamento stato) o abortire/terminare forzatamente in modo controllato (a seconda del rischio).

Il nocciolo è: nessun thread deve attendere esclusivamente per tempo (Sleep) o restare bloccato esclusivamente su I/O senza considerare in parallelo un segnale di Stop. Invece lavori con funzioni di attesa che considerano segnali multipli (es. „Stop-Event oppure Work-Event“), oppure incapsuli l’I/O con timeout più controlli di Stop.

Pensare correttamente il Stop-Timeout: SCM-Timeout vs. proprio shutdown-timeout

Qui avvengono nella maggior parte dei progetti i fraintendimenti. Esistono due diversi livelli di timeout:

  • Aspettativa SCM: Windows si aspetta che tu nello stato SERVICE_STOP_PENDING segnali regolarmente i progressi. Altrimenti sembra che tu sia bloccato. Delphi se ne occupa in parte, ma non appena ti blocchi a lungo devi avere una strategia per consentire ulteriori aggiornamenti di stato (o per mantenere breve la tua fase di stop).
  • Il tuo timeout di shutdown: definisci ad es. „Ci concediamo 20 Sekunden, um laufende Jobs sauber abzuschließen, dann brechen wir ab.“ Questo è una decisione di architettura: consistenza dei dati vs. obbligo di reboot vs. requisiti operativi.

Praticamente significa: il tuo servizio dovrebbe arrivare rapidamente in uno stato in cui non avvia più nuove unità di lavoro e poi si limita a attendere che il lavoro in corso termini – ma non indefinitamente. E questa fase di attesa dovrebbe essere eseguita in intervalli brevi, così puoi reagire e, se necessario, registrare log.

Quanto può durare lo stop?

Non esiste un numero magico valido sempre. Per molti servizi di business un intervallo obiettivo di 5–30 secondi è realistico: tempo sufficiente per i dati „in-flight“, ma abbastanza breve per le finestre di patch. Se hai bisogno regolarmente di più tempo, spesso è un segnale che elabori unità troppo grandi per volta o che dipendenze esterne (DB/HTTP) sono senza timeout.

Implementazione con TEvent: architettura che resta stabile in esercizio

Una struttura collaudata nel servizio Delphi è la seguente (senza entrare nei dettagli del framework):

  • Un Stop-Event (TEvent, Manual Reset), che viene impostato allo stop.
  • Uno o più Worker-Threads, che nel loro ciclo principale controllano regolarmente lo Stop.
  • Eventualmente un Work-Event o una coda che segnali lavoro. I worker attendono allora «Work o Stop».
  • Una Shutdown-Phase, che fa il „join“ dei worker (cioè attende che terminino), ma con timeout.

Ciò che conta non è se usi TThread, omnithreadlibrary o un pool proprio, ma che i tuoi worker non girino „alla cieca“. Un loop del worker dovrebbe strutturalmente assomigliare a: attendere evento(i) → lavoro in piccoli chunk → controllare lo Stop tra i chunk → rilasciare le risorse in modo pulito.

Trappola: Terminate da solo non basta

Molti thread di Delphi vengono „terminati“ con Terminate. Ma è solo un flag. Se il thread è bloccato in una API bloccante, inizialmente non succede nulla. Per questo un proprio Stop-Event è così utile: puoi integrarlo nelle chiamate di attesa e innescare wakeup mirati.

Trappola: FreeOnTerminate nel contesto del service

Nei service si vede spesso FreeOnTerminate := True. Questo può funzionare, ma rende più difficile controllare lo shutdown, perché spesso non avrai più una referenza chiara per attendere la terminazione del thread e registrare stati di errore. Per una logica di stop controllata è generalmente più stabile possedere i thread in modo esplicito e, nello shutdown, attenderli e rilasciarli in modo deterministico.

Operazioni bloccanti: come renderle interrompibili

Scena di troubleshooting: connessione di rete come causa di chiamate bloccanti e timeout
I/O bloccante senza timeout è la causa più frequente di blocchi di Stop nel servizio.

La parte delicata non è l’evento in sé, ma i punti in cui il tuo servizio si blocca. Tre classi tipiche:

1) Sostituire Sleep/Polling: Wait con Stop-Event

Se lavori in modo periodico („controlla ogni 10 secondi“), non usare Sleep(10000), ma attendi un evento con timeout. Così il tuo Stop-Event può terminare immediatamente l’attesa. Questo riduce la latenza di Stop e evita la sensazione „il servizio non risponde“.

2) Consumer della coda: combinare Work-Event + Stop-Event

Se hai un’architettura producer/consumer (p. es. i job vengono messi in una coda), ti serve un segnale che risvegli i consumer. Spesso è un ulteriore TEvent (Work available). Il consumer attende allora su due handle: „Work“ o „Stop“. In fase di Stop imposti lo Stop-Event e, se necessario, anche il Work-Event, in modo che tutti i consumer escano garantiti dall’attesa.

3) Chiamate esterne (DB/HTTP): timeout e percorsi di interruzione

Per gli accessi al database o le chiamate HTTP si decide se il tuo servizio si arresta correttamente. Per l’esercizio vale: Nessuna chiamata senza timeout. Un timeout non è un lusso, ma un requisito per la gestibilità. Inoltre dovresti verificare lo Stop tra le fasi di retry/backoff. Altrimenti hai il classico: „Il servizio non si arresta perché sta facendo 10 retry con Sleep“.

In alcune librerie puoi attivare esplicitamente l’annullamento (p. es. l’annullamento di una query). Se questo non è possibile, devi almeno configurare timeout sufficientemente brevi da non superare il timeout di shutdown.

Stop Pending corretto: stato, logging e gestione delle aspettative

Misurazione dei tempi e analisi dei log per la diagnosi dei Stop-Timeout nei servizi Windows
I log per fase e la misurazione dei tempi rendono i Stop-Timeout riproducibili e spiegabili.

Quando un servizio si arresta, dal punto di vista operativo è importante capire dove si blocca. A questo servono due cose:

  • Log-Marker nel percorso di Stop: „Stop richiesto“, „nessun nuovo job“, „in attesa dei worker“, „Worker X terminato“, „shutdown completato“.
  • Tempi misurabili: Quanto dura lo Stop? Quale fase consuma tempo? Spesso è sufficiente una misura temporale monotona come GetTickCount64 o TStopwatch (monotona = non distorta da cambi di orologio di sistema).

Se nel percorso di stop scrivi un solo record di log «Stopping…», il debugging sul campo resta un gioco d’indovinelli. In produzione i log sono spesso l’unica cosa che ottieni senza interazione.

Quali log sono nei servizi davvero utili?

  • PID del servizio, ora di avvio, versione/build (senza overhead eccessivo).
  • Numero di worker attivi, numero di job in-flight.
  • Dipendenze esterne attive: «chiamata al DB in corso», «richiesta HTTP in corso», «flush del file in corso» (solo aggregato, non ogni singolo dettaglio).
  • Timeout di stop raggiunto: quali worker sono ancora aperti?

Debugging sul campo: rendere riproducibile invece di indovinare

I problemi di stop si manifestano volentieri solo in produzione: carico diverso, latenze diverse, permessi diversi, finestre di patch diverse. Alcune leve collaudate in produzione:

Testare il servizio sotto controllo

  • Stop durante l’elaborazione attiva (non a idle).
  • Stop durante una anomalia esterna: DB temporaneamente non raggiungibile, endpoint HTTP lento, file share indisponibile.
  • Stop immediatamente dopo l’avvio (race condition: worker ancora in fase di setup).

Visualizzatore eventi e segnali del Service Control Manager

Windows registra gli eventi di servizio, ma spesso sono troppo generici. Meglio se il tuo servizio scrive direttamente in un file di log o nel registro eventi di Windows. Importante: il logging deve ancora funzionare nel percorso di stop. Se rilasci il logger troppo presto durante lo shutdown o il flush viene bloccato, perdi proprio le tracce decisive.

Rendere visibili i thread bloccati

Se vedi ripetutamente «Stop Timeout», vale la pena esaminare gli stati dei thread (es. con debugger/procdump in un ambiente di test). Spesso trovi un thread in stato di wait su un handle che non viene mai segnalato, o in una chiamata di rete senza timeout. La correzione raramente è «ancora più Sleep», ma un percorso di terminazione pulito.

Quando vale davvero la pena l’impegno?

Un servizio minimale, che ha solo un timer e nessuna dipendenza esterna, a volte può semplicemente «fermarsi». Ma appena si verifica una delle condizioni seguenti, un Graceful Shutdown ben progettato è quasi sempre giustificato:

  • Il servizio elabora job con effetti collaterali (scrittura di file, transazioni DB, chiamate API).
  • Ci sono più thread o un pool.
  • Il servizio dipende da risorse di rete (DB, REST, message broker, file share).
  • Il reparto operativo richiede finestre di manutenzione pianificate (reboot, aggiornamenti, failover).

Il valore aggiunto non è «eleganza», ma affidabilità operativa: meno aborti forzati del processo, meno stati intermedi incoerenti, meno interventi manuali.

Trappole pratiche: cosa spesso va storto nello shutdown

1) Lo stop è stato impostato, ma continuano ad entrare nuovi job

Se accetti lavoro in ingresso (es. tramite socket, trigger su file, timer), nel percorso di stop devi prima fermare la accettazione di nuovo lavoro: chiudere i listener, disattivare i timer, fermare lo scheduler. Altrimenti rincorrerai la terminazione perché continuano a partire nuovi job.

2) Il cleanup blocca (flush, close, finalize)

«Solo un ultimo flush» può essere pericoloso nel contesto di un servizio, se la destinazione (unità di rete, log remoto, DB) è bloccata in quel momento. Quindi: cleanup sì, ma con tempo limitato. Se necessario devi decidere quali dati tenere in memoria e perdere, piuttosto che bloccare completamente lo stop.

3) Lock e ordine

Durante lo Stop accedi spesso alle stesse strutture dati dei Worker (Queues, Caches, States). Se il Stop-Thread detiene dei Lock e poi attende la terminazione dei Worker, mentre i Worker necessitano dello stesso Lock, si verifica un Stop-Deadlock. Contromisure: mantenere brevi i tempi di holding dei Lock, non „attendere sotto Lock“ nel percorso di Stop, definire un ordine chiaro.

4) Concorrenza in caso di Stop duplicato

In pratica lo Stop può essere triggerato più volte (es. Stop + Shutdown, o lo Stop arriva nuovamente). Il tuo Stop-Pfad dovrebbe essere idempotent: impostare lo Stop-Event va bene, ma la logica duplicata di Join/Free deve essere protetta in modo sicuro (es. tramite un flag atomico).

Prospettiva operativa: cosa si aspettano amministratori e IT-Lead dal servizio

Per il reparto operativo e l’amministrazione, alla fine non conta quanto „bello“ sia il codice, ma se il servizio:

  • in caso di Stop termina in modo affidabile (prevedibile, senza blocchi),
  • in caso di Stop non produce dati incoerenti (es. file incompleti, transazioni aperte),
  • in caso di errore fornisce log utilizzabili,
  • per le finestre di manutenzione e i deployment è prevedibile.

Questo è anche il motivo per cui il tema del Stop-Timeout non è solo „roba da sviluppatori“: influisce sui cicli di patch, sui tempi di recovery e sulla possibilità stessa di avere deployment automatizzati.

Linee guida concrete per un design di shutdown robusto

Se vuoi standardizzare pragmaticamente l’argomento, queste linee guida si sono dimostrate efficaci:

  • Un Stop-Event globale, Manual Reset, creato presto nel ciclo di vita del servizio, rilasciato tardi.
  • Nessun Sleep nei loop dei Worker senza un’alternativa compatibile con lo Stop (Wait con timeout).
  • Tutte le chiamate esterne con timeout (DB, HTTP, Fileshares). Scegli timeout tali da rientrare nel tuo Shutdown-Timeout.
  • Stop-Timeout come configurazione (es. in INI/Registry), così l’esercizio operativo può reagire senza ricompilare.
  • Modello a fasi: prima graceful (concludere i job in corso), poi opzionale „soft abort“ (nessun nuovo step), quindi exit forzato come ultima risorsa.
  • Buoni Stop-Logs con fasi e misurazione dei tempi.

Conclusione: TEvent + Stop-Timeout non è un lusso, ma gestibilità

Un Stop bloccato raramente è un errore isolato – di solito è un buco architetturale: il lavoro gira in thread o in chiamate bloccanti che non conoscono un segnale di Stop comune. Con un chiaro Stop-Event (TEvent, Manual Reset), Wait compatibili con lo Stop invece di Sleep, timeout coerenti per le dipendenze esterne e un Shutdown-Timeout definito ottieni un servizio prevedibile nell’operatività quotidiana.

Il codice ripaga soprattutto se il tuo servizio opera in ambienti di produzione con finestre di manutenzione, deployment automatizzati o effetti collaterali critici. In quel contesto il „Graceful Shutdown“ non è estetica, ma un elemento per un’operatività stabile e meno escalation al prossimo reboot.

Se vuoi impostare correttamente il vostro Stop-Pfad o verificare la logica di shutdown e la sicurezza operativa di un servizio Delphi esistente, una call tecnica di sparring è spesso la via più rapida per ottenere misure chiare: prendere contatto.

Per questo tema sono inoltre importanti Delphi Windows Service e Tevent Delphi. L’articolo inquadra questi aspetti in modo comprensibile e mostra cosa conta nella pratica.

Discutere un progetto o un intervento di modernizzazione con Net-Base.

Passo successivo

Quando un tema diventa un progetto reale, architettura, sistemi esistenti e gestione operativa dovrebbero essere considerati insieme fin dalle fasi iniziali.

Non forniamo solo supporto per questioni isolate, ma anche quando da frammenti di codice sorgente, tematiche legacy o idee di portale deve nascere un progetto aziendale solido.

  • Stato attuale, stato obiettivo e rischi tecnici vengono valutati insieme.
  • REST, l'accesso ai dati, i portali e il rollout non vengono rinviati a fasi successive.
  • Vede in anticipo quale percorso è economicamente e operativamente sostenibile.

Condividi il post

Condividi direttamente questo articolo

LinkedIn, X, XING, Facebook, WhatsApp e e-mail sono immediatamente disponibili. Per Instagram prepariamo direttamente il link e un breve testo.

E-mail

Instagram si apre in una nuova scheda. Il link e il breve testo vengono copiati prima negli appunti.