Net-Base Magazín

02.08.2026

Windows služba v Delphi: Řádné ukončení pomocí TEvent a správná implementace časového limitu pro zastavení

Když se Windows-service při zastavování zablokuje, není to náhoda: většinou blokují vlákna, I/O nebo smyčky se Sleep bez cesty pro přerušení. Tento praktický příspěvek ukazuje, jak v Delphi pomocí TEvent implementovat čistý graceful shutdown a korektně ošetřit stop-timeouty...

02.08.2026

Od tématu magazínu k projektové praxi

Vhodné stránky služeb a technické stránky k příspěvku

Jedna Windows Service in Delphi působí v běžném provozu často nespektakulárně: běží na pozadí, zpracovává joby, zapisuje logy, komunikuje s databázemi nebo REST-API. Až někdo klikne „Zastavit službu“ – nebo nastane patch-reboot – a služba se nezastaví čistě. Tehdy konzole služeb po minutách zobrazuje „Wird beendet…“, služba uvízne ve stavu Stop Pending, a v nejhorším případě je proces násilně ukončen. Právě zde se vyplatí věnovat se tématu Windows Service in Delphi Graceful Shutdown jako jasnému architekturnímu tématu: s konzistentním shutdown-signálem, definovanými timeouty a vlákny, která skutečně reagují.

Tento článek neřeší internals nějakého frameworku, ale praktický vzor: TEvent jako stop-signál (kernelově blízký synchronizační objekt z System.SyncObjs), v kombinaci se strategií Stop-Timeout, která bere v úvahu jak Windows Service Control Manager (SCM, tedy tu Windows-komponentu, která služby spouští/zastavuje), tak vlastní worker-vlákna. Dále typické okrajové případy, přístupy k debugování a otázka, kdy má smysl si tu přidanou logiku přidat.

Windows Service in Delphi Graceful Shutdown v praxi

Nejčastější příčina je jednoduchá: služba má alespoň jedno vlákno, které je uvězněné v blokující operaci a nezná žádnou cestu pro přerušení. Klasika:

  • Polling-smyčky se Sleep: „while not Terminated do Sleep(1000)“. Při stopu přijde signál, ale vlákno zareaguje až po (až) 1 sekundě (nebo 30 sekundách…).
  • Blokující I/O: volání databáze, HTTP-requests, Named Pipes, čekání na souborový systém – cokoliv, co prostě „čeká“, aniž by sledovalo stop-signál.
  • Queue-consumer bez probuzení: worker čeká na frontu, ale při stopu není probuzen, aby mohl vystoupit.
  • Pořadí zámků / deadlocky: při stopu probíhá „cleanup“, zatímco jiná vlákna drží zámky. To se často projeví pouze v stop-cestě, protože pořadí tam bývá jiné než v normálním provozu.

Windows SCM očekává, že služba na příkaz Stop zareaguje pohotově a nebude průběžně hlásit svůj stav (pomocí SetServiceStatus; Delphi to zabalí do servisní komponenty). Pokud sice přijmeš stop-událost, ale vlákna nesestavíš čistě dolů, proces zůstane naživu – a Windows nakonec usoudí, že to „trvá příliš dlouho“. Výsledkem je buď násilné ukončení, nebo služba, která uvízne v nejasném mezistavu.

Grundprinzip: Ein Stop-Signal, das jeder Worker versteht

Abstrakte Grafik: Worker-Threads warten auf Work oder Stop-Event und beenden geordnet
Když workery čekají na „práci nebo stop“, klesne stop-latence bez pollingu.

Graceful Shutdown funguje pouze tehdy, máš-li signál, který:

  • může být sledován všemi relevantními vlákny,
  • také z blokujících čekacích stavů dokáže projevit účinek,
  • v průběhu ukončování deterministický je (žádná „možná se jednou dostane ven“-naděje),
  • má jasnou Timeout-Strategie.

In Delphi ist TEvent dafür ein sehr brauchbares Werkzeug: ein Event-Objekt, das intern über Windows-Handles umgesetzt ist (vergleichbar mit CreateEvent/SetEvent). Du kannst es als „Stop requested“-Signal verwenden. Jeder Worker wartet dann nicht einfach blind, sondern wartet „auf Arbeit oder auf Stop“.

TEvent richtig wählen: ManualReset vs. AutoReset

Bei Stop-Signalen willst du üblicherweise Manual Reset (manuell zurücksetzbar): Einmal gesetzt, bleibt das Event „signaled“, bis du es zurücksetzt. Damit ist sichergestellt, dass jeder Thread, der später in eine Wartephase kommt, das Stop-Signal trotzdem erkennt. Auto Reset wäre hier riskant, weil es das Signal nach einem wartenden Thread automatisch zurücksetzt und andere Threads das Stop-Signal verpassen könnten.

Delphi-Service-Lebenszyklus: Wo Stop wirklich ankommt

Ein Delphi-Windows- und Linux-Services basiert typischerweise auf TService (VCL/RTL). Der SCM schickt Kommandos (Start, Stop, Pause, Continue). Delphi ruft dann entsprechende Events/Methoden auf (je nach Template z. B. OnStart, OnStop, OnExecute).

Důležité pro architekturu:

  • OnStop není místo pro dlouhé čekání bez aktualizací stavu. Je to místo, kde zahájíš vypínání a pak kontrolovaně čekáš – s timeoutem.
  • OnExecute je často smyčka. Pokud tam „nekonečně“ pracuješ, musí smyčka reagovat na Stop-signál.
  • Worker-Threads (TThread oder Thread-Pools) müssen auf dasselbe Stop-Signal reagieren, sonst ist der Service logisch gestoppt, aber physisch noch nicht fertig.

Sauberes Muster: Stop-Event + Join der Worker + harter Fallback

Praktický vzor sestává ze čtyř kroků:

  1. Požádat o Stop: nastavit Stop-Event, nepřijímat nové úlohy.
  2. Vyvolat probuzení: pokud Workery čekají na frontách nebo spí, musí se umět „probudit“ (např. přes Event-/Queue-signál).
  3. Uspořádané ukončení: Workery ukončí smyčky, uzavřou zdroje (DB-Verbindungen, Dateien, Handles) a oznámí „hotovo“.
  4. Timeout und Fallback: Pokud se vše nestihne ukončit včas, musíš rozhodnout: dál čekat (s aktualizací stavu) nebo kontrolovaně přerušit/tvrdě ukončit (podle rizika).

Jádro je: žádné vlákno nesmí výlučně čekat na čas (Sleep) nebo výlučně blokovat na I/O, aniž by paralelně zohledňovalo Stop-signál. Místo toho používáš funkce čekání, které zohledňují více signálů (např. „Stop-Event nebo Work-Event“), nebo zapouzdříš I/O do timeoutů plus kontrol Stopu.

Stop-Timeout richtig denken: SCM-Timeout vs. eigener Shutdown-Timeout

Zde v projektech vzniká většina nedorozumění. Existují dva různé levely timeoutů:

  • Očekávání SCM: Windows očekává, že v stavu SERVICE_STOP_PENDING budeš pravidelně hlásit průběh. Jinak to vypadá, jako bys visel. Delphi se o to částečně stará, ale jakmile se dlouho zablokuješ, potřebuješ strategii, jak zajistit další aktualizace stavu (nebo jak udržet fázi ukončení krátkou).
  • Vlastní timeout pro vypnutí: Např. určíš „Dáváme si 20 sekund na čisté dokončení běžících úloh, pak ukončíme.“ To je architektonické rozhodnutí: konzistence dat vs. nucený reboot vs. provozní požadavky.

Prakticky to znamená: tvůj servis by měl rychle přejít do stavu, kdy nezahajuje nové pracovní jednotky, a pak jen čeká na dokončení běžících úloh – ale ne donekonečna. A tato čekací fáze by měla probíhat v krátkých intervalech, aby bylo možné reagovat a případně zapisovat logy.

Jak dlouho může trvat zastavení?

Neexistuje univerzální číslo, které by platilo vždy. Pro mnoho business služeb je realistický cílový rozsah 5–30 sekund: dost času pro „in-flight“ data, ale dost krátké pro patch okno. Pokud pravidelně potřebuješ více času, bývá to často známka, že zpracováváš příliš velké jednotky naráz nebo že externí závislosti (DB/HTTP) běží bez timeoutu.

Implementace s TEvent: struktura, která v provozu zůstane stabilní

Ověřená struktura v Delphi-servisu vypadá takto (bez rozebírání detailů frameworku):

  • Jedno Stop-Event (TEvent, Manual Reset), které se při zastavení nastaví.
  • Jeden nebo více Worker-Threads, kteří ve své hlavní smyčce pravidelně kontrolují Stop.
  • Volitelně Work-Event nebo fronta, která signalizuje práci. Workery pak čekají na „Work nebo Stop“.
  • Shutdown-Phase, která „joined“ workery (tedy čeká, dokud neskončí), ale s timeoutem.

Rozhodující není, zda používáš TThread, omnithreadlibrary nebo vlastní pool, ale aby tvoji workeři neběhali „naslepo“. Smysl worker-smyčky by měl být strukturálně takovýto: čekání na událost(i) → práce v malých částech → mezi částmi kontrola Stop → čisté uvolnění zdrojů.

Úskalí: Samotné Terminate nestačí

Mnoho vláken v Delphi se „ukončí“ pomocí Terminate. To je ale jen příznak. Pokud je vlákno právě v blokující API, nic se nejdřív nestane. Proto je vlastní Stop-Event tak užitečný: můžeš ho integrovat do čekacích volání a cíleně vyvolávat probuzení.

Úskalí: FreeOnTerminate v kontextu service

Ve službách se často vidí FreeOnTerminate := True. To může fungovat, ale ztěžuje to kontrolu shutdownu, protože často už nemáš čistou referenci, na kterou by ses mohl spolehnout při čekání na konec vlákna a protokolování chybových stavů. Pro kontrolovanou stop-logiku je obvykle stabilnější vlastnit vlákna explicitně a při shutdownu je deterministicky čekat a uvolňovat.

Blokující operace: Jak je udělat schopné zastavení

Scéna řešení problémů: síťové připojení jako příčina blokujících volání a timeoutů
Blokující I/O bez timeoutu je nejčastější důvod, proč se služba zasekne při zastavení.

Nejtěžší část není samotná událost, ale místa, kde se vaše služba blokuje. Tři typické třídy:

1) Sleep/Polling ersetzen: Wait mit Stop-Event

Pokud pracujete periodicky („kontrola každých 10 sekund“), nepoužívejte Sleep(10000), místo toho čekejte na event s timeoutem. Pak může vaše Stop-Event čekání okamžitě ukončit. To snižuje latenci při zastavení a zabraňuje dojmu „služba nereaguje“.

2) Queue-Consumer: Work-Event + Stop-Event kombinieren

Pokud máte producer/consumer architekturu (např. úlohy jsou vložené do fronty), potřebujete signál, který probudí consumer. Často je to další TEvent (Work available). Consumer pak čeká na dva handles: „Work“ nebo „Stop“. Při Stop nastavíte Stop-Event a případně i Work-Event, aby všichni consumeri jistě vyšli z Wait.

3) Externe Calls (DB/HTTP): Timeouts und Abbruchpfade

Při přístupu do databáze nebo HTTP volání se rozhoduje, zda se služba korektně zastaví. Pro provoz platí: Žádné volání bez timeoutu. Timeout není luxus, ale předpoklad pro řízení. Navíc byste měli mezi retry/backoff fázemi vždy kontrolovat Stop. Jinak máte klasiku: „služba se nezastaví, protože právě provádí 10 retry se Sleep“.

U některých knihoven lze přerušení explicitně vyvolat (např. zrušení dotazu). Pokud to není možné, musíte alespoň nakonfigurovat timeouty natolik krátké, aby nepřekročily časový limit vypnutí.

Stop Pending korrekt: Status, Logging und Erwartungsmanagement

Měření času a analýza logů pro diagnostiku stop-timeoutů u Windows služeb
Fázové logy a měření času dělají stop-timeouty reprodukovatelnými a vysvětlitelnými.

Když se služba zastavuje, je z provozního pohledu důležité pochopit, kde se zasekává. K tomu potřebujete dvě věci:

  • Log-marker v cestě zastavení: „Stop požadován“, „žádné nové úlohy“, „čekám na worker“, „Worker X ukončen“, „vypnutí dokončeno“.
  • Měřitelné časy: Jak dlouho trvá zastavení? Která fáze spotřebovává čas? Často stačí monotónní měřítko času jako GetTickCount64 nebo TStopwatch (monotónní = nezkreslené změnami systémového času).

Pokud v stopovací cestě zapíšeš do logu pouze jediný záznam „Stopping…“, zůstává ladění v terénu hrou na hádání. V provozu služby jsou logy často to jediné, co dostaneš bez interakce.

Které logy jsou u služeb opravdu užitečné?

  • Service-PID, čas spuštění, verze/Build (bez zbytečného režijního zatížení).
  • Počet aktivních workerů, počet probíhajících úloh (in-flight).
  • Aktivní externí závislosti: „DB-Call läuft“, „HTTP-Request läuft“, „Datei-Flush läuft“ (pouze agregovaně, ne každý detail).
  • Dosažení Stop-Timeoutu: které workery jsou ještě otevřené?

Ladění v terénu: reprodukovatelné místo hádání

Problémy se zastavováním se často objevují pouze v produkci: jiná zátěž, jiné latence, jiná oprávnění, jiná okna pro patchování. Několik prakticky ověřených pák:

Testuj službu pod kontrolou

  • Zastavení během aktivního zpracování (ne v idle).
  • Zastavení během externí poruchy: DB krátce nedostupná, HTTP-endpoint pomalý, fileshare pryč.
  • Zastavení bezprostředně po startu (race-conditions: workery ještě ve fázi inicializace).

Event Viewer und Service Control Manager Signale

Windows zapisuje události služby, ale ty jsou často hrubé. Lepší je, když tvá služba sama zapisuje do logovacího souboru nebo do Windows Event Logu. Důležité je: logování by v stopovací cestě mělo stále fungovat. Pokud uvolníš logger při shutdownu příliš brzy nebo je flush blokován, ztratíš právě ty rozhodující stopy.

Zviditelnit visící vlákna

Pokud opakovaně vidíš „Stop Timeout“, vyplatí se podívat na stavy vláken (např. přes debugger/Procdump v testovacím prostředí). Často najdeš vlákno ve stavu čekání na handle, který nikdy není signalizován, nebo v síťovém volání bez timeoutu. Oprava zřídka znamená „více sleepů“, spíš čistou cestu pro přerušením práce (abruptní abort je obvykle špatně).

Kdy se to úsilí opravdu vyplatí?

Minimalistická služba, která má jen timer a žádné externí závislosti, se někdy může „prostě zastavit“. Jakmile ale platí některý z následujících bodů, vyplatí se téměř vždy implementovat čistý Graceful Shutdown:

  • Služba zpracovává Jobs mit Seiteneffekten (zápis souborů, DB-transakce, volání API).
  • Existuje více vláken nebo vlákňový pool.
  • Služba je závislá na síťových zdrojích (DB, REST, Message Broker, Fileshares).
  • Provoz požaduje plánovatelná okna údržby (Reboots, Updates, Failover).

Přínos není „elegance“, ale provozní bezpečnost: méně tvrdých ukončení procesů, méně nekonzistentních mezistavů, méně manuálních zásahů.

Praktické úskalí: Co se při vypínání často pokazí

1) Stop je nastaven, ale nové joby se přesto dostávají dovnitř

Pokud přijímáš příchozí práci (např. přes Socket, Datei-Trigger, Timer), musíš v stopovací cestě nejdříve zastavit přijímání nové práce: zavřít Listenery, deaktivovat Timery, pozastavit Scheduler. Jinak budeš nahánět konec, protože stále startují nové joby.

2) Cleanup blokuje (Flush, Close, Finalize)

„Ještě rychle všechno flushnout“ může být v kontextu služby nebezpečné, pokud cíl (síťová jednotka, Remote-Log, DB) právě visí. Proto: Cleanup ano, ale s omezeným časem. V krajním případě musíš rozhodnout, která data z paměti obětuješ, místo aby celou zastávku blokovalo.

3) Locks und Reihenfolge

Beim Stop greifst du häufig auf dieselben Datenstrukturen zu wie die Worker (Queues, Caches, States). Wenn der Stop-Thread Locks hält und dann auf Worker-Ende wartet, während Worker denselben Lock brauchen, hast du einen Stop-Deadlock. Gegenmittel: Lock-Hold-Zeiten klein halten, im Stop-Pfad nicht „unter Lock warten“, klare Reihenfolge definieren.

4) Nebenläufigkeit beim doppelten Stop

In der Praxis kann Stop mehrfach getriggert werden (z. B. Stop + Shutdown, oder Stop kommt erneut). Dein Stop-Pfad sollte idempotent sein: Stop-Event setzen ist okay, aber doppelte Join/Free-Logik muss sauber geschützt werden (z. B. über ein Atomik-Flag).

Operativer Blick: Was Admins und IT-Leads vom Service erwarten

Für Betrieb und Administration zählt am Ende nicht, wie „schön“ der Code ist, sondern ob der Dienst:

  • bei Stop verlässlich endet (planbar, ohne Hänger),
  • bei Stop keine inkonsistenten Daten produziert (z. B. halbe Dateien, offene Transaktionen),
  • im Fehlerfall brauchbare Logs liefert,
  • bei Wartungsfenstern und Deployments berechenbar ist.

Das ist auch der Grund, warum das Thema Stop-Timeout nicht nur „Entwicklerkram“ ist: Es beeinflusst Patchzyklen, Recovery-Zeiten und die Frage, ob automatisierte Deployments überhaupt möglich sind.

Konkrete Leitplanken für ein robustes Shutdown-Design

Wenn du das Thema pragmatisch standardisieren willst, haben sich diese Leitplanken bewährt:

  • Ein globales Stop-Event, Manual Reset, früh im Service-Lebenszyklus erstellt, spät freigegeben.
  • Kein Sleep in Worker-Loops ohne Stop-fähige Alternative (Wait mit Timeout).
  • Alle externen Calls mit Timeouts (DB, HTTP, Fileshares). Timeouts so wählen, dass sie in deinen Shutdown-Timeout passen.
  • Stop-Timeout als Konfiguration (z. B. in INI/Registry), damit Betrieb reagieren kann, ohne neu zu kompilieren.
  • Stufenmodell: Erst graceful (laufende Jobs zu Ende), dann optional „soft abort“ (keine neuen Schritte), dann harter Exit als letzter Ausweg.
  • Gute Stop-Logs mit Phasen und Zeitmessung.

Fazit: TEvent + Stop-Timeout ist kein Luxus, sondern Steuerbarkeit

Ein hängender Stop ist selten ein Einzelfehler – meist ist es ein Architekturloch: Arbeit läuft in Threads oder blockierenden Calls, die kein gemeinsames Stop-Signal kennen. Mit einem klaren Stop-Event (TEvent, Manual Reset), stop-fähigen Waits statt Sleep, konsequenten Timeouts für externe Abhängigkeiten und einem definierten Shutdown-Timeout bekommst du einen Service, der im Alltag berechenbar ist.

Der Code lohnt sich besonders, wenn dein Service in Produktionsumgebungen mit Wartungsfenstern, automatisierten Deployments oder kritischen Seiteneffekten arbeitet. Dann ist „Graceful Shutdown“ nicht Kosmetik, sondern ein Baustein für stabilen Betrieb und weniger Eskalationen beim nächsten Reboot.

Wenn du euren Stop-Pfad einmal sauber aufsetzen oder einen bestehenden Delphi-Service auf robuste Shutdown-Logik und Betriebssicherheit überprüfen willst, ist ein technischer Sparrings-Call oft der schnellste Weg zu klaren Maßnahmen: Kontakt aufnehmen.

Für dieses Thema sind auch Delphi Windows Service und Tevent Delphi wichtig. Der Beitrag ordnet diese Aspekte verständlich ein und zeigt, worauf es im Alltag ankommt.

Projekt oder Modernisierungsvorhaben mit Net-Base besprechen.

další krok

Když se z tématu stane reálný projekt, měly by být architektura, stávající systém a provoz posuzovány společně již v rané fázi.

Podporujeme nejen při jednotlivých otázkách, ale i v případě, že se z útržků zdrojového kódu, legacy témat nebo nápadů na portál má vyvinout robustní podnikový projekt.

  • Současný stav, cílový stav a technická rizika jsou hodnoceny společně.
  • REST, přístup k datům, portály a rollout nebudou přesunuty do pozdějších fází.
  • Včas zjistíte, která varianta je ekonomicky i provozně životaschopná.

Sdílet příspěvek

Sdílet tento příspěvek přímo

LinkedIn, X, XING, Facebook, WhatsApp a e-mail jsou ihned k dispozici. Pro Instagram připravíme odkaz a krátký text.

E-mail

Instagram se otevře v nové záložce. Odkaz a krátký text budou předtím zkopírovány do schránky.