Net-Base Magazin

02.08.2026

Windows szolgáltatás Delphi-ben: a zavartalan leállítás helyes megvalósítása TEvent és Stop-Timeout használatával

Ha egy Windows-szolgáltatás leállítás közben „elakad”, az ritkán véletlen: többnyire szálak, I/O-műveletek vagy megszakítási út nélküli Sleep-ciklusok blokkolják a végrehajtást. Ez a gyakorlati bejegyzés megmutatja, hogyan valósítsd meg Delphi-ben TEvent segítségével a zökkenőmentes leállítást, és hogyan kezeld helyesen a leállítási időtúllépéseket...

02.08.2026

A magazintémától a projektgyakorlatig

A bejegyzéshez tartozó szolgáltatási és technikai oldalak

Egy Windows Service in Delphi a mindennapokban gyakran nem feltűnő: háttérben fut, feldolgozza a feladatokat, írja a logokat, kapcsolódik adatbázisokhoz vagy REST-API-khoz. A gond akkor kezdődik, amikor valaki „Szolgáltatás leállítása” gombra kattint — vagy beütemeznek egy javító újraindítást — és a szolgáltatás nem áll le tisztán. Ekkor a Szolgáltatások konzol percekig „Leállítás…”-t mutat, a szolgáltatás Stop Pending állapotban ragad, és rosszabb esetben a folyamatot keményen leállítják. Pont itt érdemes a Windows Service in Delphi Graceful Shutdown-t tudatos architekturális kérdésként kezelni: egy egyértelmű leállítási jel, meghatározott timeoutok és olyan szálak, amelyek valóban reagálnak.

Ebben a bejegyzésben nem a keretrendszer belső működésével foglalkozunk, hanem egy gyakorlati mintával: TEvent mint leállítási jel (egy kernelközeli szinkronizációs objektum a System.SyncObjs-ból), kombinálva egy Stop-Timeout-stratégiával, amely figyelembe veszi mind a Windows Service Control Managert (SCM, tehát az a Windows-komponens, amely elindítja/leállítja a szolgáltatásokat), mind a saját munkaszálakat. Emellett tipikus szélső esetek, hibakeresési megközelítések és az a kérdés, mikor érdemes ténylegesen a plusz logikát beépíteni.

Windows Service in Delphi Graceful Shutdown a gyakorlatban

A leggyakoribb ok egyszerű: a szolgáltatásnak legalább egy olyan szála van, amely egy blokkoló műveletben ragadt, és nincs megszakítási útvonala. Tipikus esetek:

  • Polling ciklusok Sleep-tel: „while not Terminated do Sleep(1000)”. A leállítási jel megérkezik, de a szál csak legfeljebb 1 másodperc (vagy 30 másodperc…) múlva reagál.
  • Blokkoló I/O: adatbázis-hívások, HTTP-kérések, Named Pipes, fájlrendszer-várakozások – minden, ami „csak vár”, és nem figyel a leállítási jelre.
  • Queue-fogyasztó ébresztés nélkül: egy worker egy sorra vár, de leálláskor nem ébresztik fel kilépéshez.
  • Lock-sorrend / Deadlockok: a leállítás során takarítást végeznek, miközben más szálak még tartanak zárakat. Ez gyakran csak a leállítási ágban jelentkezik, mert ott a sorrend eltér a normál működéstől.

Az Windows SCM azt várja el, hogy egy szolgáltatás gyorsan reagáljon egy Stop-parancsra és folyamatosan jelentse az állapotát (a SetServiceStatus híváson keresztül; Delphi ezt beágyazza a Service-komponensbe). Ha ugyan elfogadod a Stop-eseményt, de a szálaidat nem állítod le tisztán, a folyamat életben marad — és az Windows előbb-utóbb úgy dönt, hogy „túl sokáig tart”. Az eredmény vagy egy kemény leállítás, vagy egy szolgáltatás, amely egy bizonytalan köztes állapotban ragad.

Alapelv: egy leállítási jel, amelyet minden munkaszál ért

Absztrakt grafika: munkaszálak munkára vagy leállítási eseményre várnak és rendezett módon fejezik be
Ha a munkaszálak „Munka vagy Leállítás” között várnak, a leállítási késleltetés csökken polling nélkül.

Egy Graceful Shutdown csak akkor működik, ha van egy jel, amely:

  • minden releváns szál által észlelhető,
  • még blokkoló várakozási állapotokból is hatást fejt ki,
  • a Stop-útvonalon determininsztikus (nem a „talán egyszer kijön”-remény),
  • egyértelmű Timeout-Strategie-val rendelkezik.

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).

Wichtig für die Architektur:

  • OnStop ist kein Ort für langes Warten ohne Status-Updates. Es ist der Ort, an dem du den Shutdown anstößt und dann kontrolliert wartest – mit Timeout.
  • OnExecute ist oft eine Schleife. Wenn du dort „endlos“ arbeitest, muss die Schleife auf ein Stop-Signal reagieren.
  • 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

Das praxistaugliche Muster besteht aus vier Schritten:

  1. Stop anfordern: Stop-Event setzen, keine neuen Jobs mehr annehmen.
  2. Wakeups auslösen: Wenn Worker auf Queues oder Sleeps warten, müssen sie „aufwachen“ können (z. B. über Event/Queue-Signal).
  3. Geordnet beenden: Worker beenden ihre Loops, schließen Ressourcen (DB-Verbindungen, Dateien, Handles) und melden „fertig“.
  4. Timeout und Fallback: Wenn nicht alles rechtzeitig endet, musst du eine Entscheidung treffen: weiter warten (mit Status-Update) oder kontrolliert abbrechen/hart beenden (je nach Risiko).

Der Kern ist: kein Thread darf ausschließlich auf Zeit warten (Sleep) oder ausschließlich auf I/O blockieren, ohne parallel ein Stop-Signal zu berücksichtigen. Stattdessen arbeitest du mit Wartefunktionen, die mehrere Signale berücksichtigen (z. B. „Stop-Event oder Work-Event“), oder du kapselst I/O in Timeouts plus Stop-Checks.

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

Hier passieren in Projekten die meisten Missverständnisse. Es gibt zwei verschiedene Timeout-Ebenen:

  • SCM-Erwartung: Windows erwartet, dass du im Status SERVICE_STOP_PENDING regelmäßig Fortschritt meldest. Sonst wirkt es so, als hängst du. Delphi kümmert sich teilweise darum, aber sobald du selbst länger blockierst, brauchst du eine Strategie, wie du weiter Status-Updates ermöglichst (oder wie du deine Stop-Phase kurz hältst).
  • Dein eigener Shutdown-Timeout: Du definierst z. B. „Wir geben uns 20 Sekunden, um laufende Jobs sauber abzuschließen, dann brechen wir ab.“ Das ist ein Architekturentscheid: Datenkonsistenz vs. Reboot-Zwang vs. Betriebsvorgaben.

Gyakorlatilag ez azt jelenti: a szolgáltatásodnak gyorsan olyan állapotba kell kerülnie, hogy ne indítson több új munkaegységet, és utána csak arra várjon, hogy a futó munkák befejeződjenek – de nem végtelen ideig. Ez a várakozási fázis kis időközönként fusson, hogy reagálni tudj és szükség esetén naplózz.

Meddig tarthat a leállás?

Nincs egyetlen varázsszám, ami mindig megfelel. Sok üzleti szolgáltatásnál reális célérték a 5–30 másodperc: elegendő idő az „in-flight” adatokra, de elég rövid a patch-ablakokhoz. Ha rendszeresen ennél hosszabb időre van szükséged, az gyakran arra utal, hogy túl nagy egységeket dolgozol fel egyszerre, vagy hogy külső függőségek (DB/HTTP) időkorlát nélkül futnak.

Megvalósítás TEvent-tel: olyan felépítés, amely üzem közben stabil marad

Egy bevált felépítés a Delphi-szolgáltatásban így fest (anélkül, hogy a keretrendszer részleteit kimerítenénk):

  • Egy Stop-Event (TEvent, Manual Reset), amely a leállításkor kerül beállításra.
  • Egy vagy több worker-szál, amelyek a főciklusukban rendszeresen ellenőrzik a leállítási jelzést.
  • Opcionálisan egy Work-Event vagy egy munka jelzésére szolgáló sor (queue). A worker-ek ekkor „Work vagy Stop”-ra várnak.
  • Egy Shutdown-fázis, amely ‚joined‘-olja a worker-eket (azaz vár, amíg befejeződnek), de időkorláttal.

A lényeg nem az, hogy TThread, omnithreadlibrary vagy egy saját poolt használsz-e, hanem hogy a worker-eid ne fussanak „vakon”. A worker-ciklus szerkezetileg így nézzen ki: Várakozás esemény(ek)re → munka kis darabokban → darabok között leállítás ellenőrzése → erőforrások tiszta felszabadítása.

Buktató: Terminate önmagában nem elég

Sok Delphi-szálat Terminate-tel „megszakítanak”. Ez azonban csak egy jelző. Ha a szál épp egy blokkoló API-ban van, először semmi sem történik. Ezért hasznos egy külön Stop-Event: beépítheted várakozó hívásokba és célzott ébresztéseket indíthatsz.

Buktató: FreeOnTerminate a szolgáltatás kontextusában

Szolgáltatásoknál gyakran látni a FreeOnTerminate := True-t. Ez működhet, de nehezebbé teszi a leállítás kontrollálását, mert gyakran nincs többé tiszta referenciád, amivel a szál befejeződésére várhatnál és hibállapotokat naplózhatnál. A kontrollált leállítási logika számára általában stabilabb, ha a szálakat expliciten birtoklod és a leállítás során determinisztikusan vársz rájuk és felszabadítod őket.

Blokkoló műveletek: így teszed őket leállíthatóvá

Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
A blokkoló I/O időtúllépés nélkül a szolgáltatás leállása közbeni akadozások leggyakoribb oka.

A trükkös rész nem maga az esemény, hanem azok a helyek, ahol a szolgáltatásod blokkolódik. Három tipikus osztály:

1) Sleep/Polling helyettesítése: várakozás Stop-Event-tel

Ha periodikusan dolgozol („10 másodpercenként ellenőrizni”), ne használj Sleep(10000)-et, hanem várj egy eseményre időtúllépéssel. Így a Stop-Event azonnal megszakíthatja a várakozást. Ez csökkenti a leállítási késleltetést és megakadályozza a „Service reagiert nicht” érzetét.

2) Queue-Consumer: Work-Event + Stop-Event kombinálása

Ha Producer/Consumer-architektúrát használsz (pl. a feladatok egy sorba kerülnek), szükséged van egy jelre, ami felébreszti a consumer-eket. Gyakran ez egy további TEvent (Work available). A consumer ezután két handle-re vár: „Work” vagy „Stop”. Leállításkor állítsd be a Stop-Event-et és szükség esetén a Work-Event-et is, hogy minden consumer garantáltan kilépjen a Wait-ből.

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

Adatbázis-hozzáféréseknél vagy HTTP-hívásoknál dől el, hogy a szolgáltatás tisztán leáll-e. Üzemeltetési szempontból: Nincs Call időtúllépés nélkül. Az időtúllépés nem luxus, hanem a kezelhetőség előfeltétele. Ezen felül mindig ellenőrizd a Stop-ot az újrapróbálkozások/Backoff-fázisok között. Ellenkező esetben megkapod a klasszikust: „A szolgáltatás nem áll le, mert éppen 10 retry-t hajt végre Sleep használatával.”

Néhány könyvtárnál explicit módon triggerelheted a megszakításokat (pl. lekérdezés-megszakítás). Ha ez nem lehetséges, legalább konfiguráld úgy az időtúllépéseket, hogy elég rövidek legyenek ahhoz, hogy ne lépjék túl a leállítási időkorlátot.

Stop Pending korrekt: státusz, naplózás és elváráskezelés

Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
Fázisnaplók és időmérés révén a Stop-timeoutok reprodukálhatók és magyarázhatók.

Ha egy szolgáltatás leáll, üzemeltetési szempontból fontos, hogy értsd, hol akad el. Ehhez két dolog kell:

  • Log-marker a leállítási úton: „Leállítás kérése”, „nincsenek új feladatok”, „várás a worker(ek)re”, „Worker X befejezett”, „leállítás kész”.
  • Mérhető idők: Mennyi ideig tart a leállás? Melyik fázis emészti a legtöbb időt? Itt gyakran elegendő egy monoton időmérő, például GetTickCount64 vagy TStopwatch (monoton = a rendszeridő-változások nem torzítják).

Ha a leállítási útvonalon csak egyetlen naplóbejegyzést írsz: „Stopping…“, a terepen a hibakeresés találgatássá válik. Szolgáltatás-üzemeltetésben a naplók gyakran az egyetlen információ, amit interakció nélkül kapsz.

Mely naplók hasznosak valóban a szolgáltatásoknál?

  • Service-PID, indítási idő, verzió/Build (túlzott overhead nélkül).
  • Aktív worker-ek száma, folyamatban lévő (in-flight) feladatok száma.
  • Aktív külső függőségek: „DB-hívás folyamatban“, „HTTP-kérelem folyamatban“, „fájl-flush folyamatban“ (csak aggregáltan, nem minden részlet).
  • Stop-timeout elérve: mely worker-ek vannak még nyitva?

Hibakeresés a terepen: reprodukálhatóvá tenni a találgatás helyett

A leállási problémák gyakran csak a termelésben jelentkeznek: más terhelés, más késleltetések, más jogosultságok, más javítási időablakok. Néhány, a gyakorlatban bevált beavatkozás:

Szolgáltatás kontrollált tesztelése

  • Leállítás aktív feldolgozás közben (ne tétlen állapotban).
  • Leállítás külső zavar idején: DB rövid ideig elérhetetlen, HTTP-végpont lassú, fájlkiszolgáló eltűnik.
  • Leállítás közvetlenül indítás után (versenyfeltételek: worker-ek még inicializálás alatt).

Event Viewer und Service Control Manager Signale

Windows ír szolgáltatási eseményeket, de ezek gyakran durvák. Jobb, ha a szolgáltatásod közvetlenül egy naplófájlba vagy a Windows Event Log-ba ír. Fontos: a naplózásnak a leállítási útvonalon is működnie kell. Ha a loggert a leállításkor túl korán felszabadítod, vagy a flush blokkolva van, éppen a döntő nyomokat veszíted el.

Függőben lévő szálak láthatóvá tétele

Ha ismételten „Stop Timeout”-ot látsz, érdemes megnézni a szálak állapotát (pl. debuggerrel/Procdump-pal tesztkörnyezetben). Gyakran találsz egy szálat várakozó állapotban egy olyan handle-en, amit soha nem jeleznek, vagy egy hálózati hívást időkorlát nélkül. A javítás ritkán az, hogy „még több sleep”, hanem egy tiszta leállítási útvonal.

Mikor éri meg ténylegesen a ráfordítás?

Egy minimalista szolgáltatás, amely csak egy timert használ és nincsenek külső függőségei, néha „egyszerűen leáll”. Azonban ha bármelyik alábbi feltétel igaz, egy tiszta Graceful Shutdown szinte mindig megéri:

  • A szolgáltatás mellékhatásos feladatokat dolgoz fel (fájlok írása, DB-tranzakciók, API-hívások).
  • Vannak több szál vagy egy pool.
  • A szolgáltatás hálózati erőforrásoktól függ (DB, REST, Message Broker, fájlkiszolgálók).
  • Az üzemeltetés tervezhető karbantartási ablakokat követel (újraindítások, frissítések, failover).

Az értéknövekedés nem az „elegancia”, hanem az üzemeltetési biztonság: kevesebb kemény folyamatleállás, kevesebb inkonzisztens átmeneti állapot, kevesebb manuális beavatkozás.

Gyakorlati buktatók: mi szokott elromlani leállításkor

1) Leállítás kezdeményezve, de új feladatok mégis érkeznek

Ha bejövő munkát fogadsz (pl. socketen, fájl-triggerrel, timerrel), a leállítási útvonalban először a új munkák fogadását kell leállítanod: listener bezárása, timer kikapcsolása, scheduler leállítása. Különben a végét fogod üldözni, mert mindig új feladatok indulnak.

2) Takarítás blokkolva (Flush, Close, Finalize)

„Csak még gyorsan mindent flush-olok” veszélyes lehet szolgáltatási kontextusban, ha a cél (hálózati meghajtó, távoli napló, DB) épp akad. Ezért: cleanup igen, de korlátozott idővel. Szükség esetén el kell döntened, mely adatok vesznek el a memóriában, ahelyett hogy a teljes leállást blokkolnád.

3) Zárolások und Reihenfolge

A leállítás során gyakran ugyanazokra az adatszerkezetekre támaszkodsz, mint a Worker-ek (sorok, gyorsítótárak, állapotok). Ha a leállítási szál zárokat tart, majd a Worker-ek befejezésére vár, miközben a Worker-ek ugyanazt a zárolást igénylik, leállítási holtpont (Stop-Deadlock) alakul ki. Ellenintézkedés: a zárolás megtartási idejét tartsd röviden, a leállítási útvonalon ne várakozz „zár alatt”, és határozz meg egyértelmű sorrendet.

4) Párhuzamosság kettős leállításnál

Gyakorlatban a leállítás többször is kiváltható (pl. Stop + Shutdown, vagy a Stop ismét megtörténik). A leállítási útvonaladnak idempotensnek kell lennie: a leállítási esemény beállítása rendben van, de a duplikált Join/Free-logikát gondosan védeni kell (pl. egy atomikus jelzővel).

Operatív szemszög: mit várnak az adminisztrátorok és az IT-vezetők a szolgáltatástól

Üzemeltetés és adminisztráció szempontjából a végén nem az számít, hogy mennyire „szép” a kód, hanem hogy a szolgáltatás:

  • leállításkor megbízhatóan befejeződik (tervezhető, akadások nélkül),
  • leállításkor nem hoz létre inkonzisztens adatokat (pl. félbehagyott fájlok, nyitott tranzakciók),
  • hiba esetén használható naplókat szolgáltat,
  • karbantartási ablakok és telepítések során előrelátható legyen.

Ez az oka annak is, hogy a leállítási időkorlát nem csupán „fejlesztői ügy”: befolyásolja a patch-ciklusokat, a helyreállítási időket és azt, hogy egyáltalán lehetségesek-e az automatizált telepítések.

Konkrét iránymutatások robosztus leállítási tervekhez

Ha pragmatikusan szeretnéd standardizálni a témát, ezek az iránymutatások bizonyultak hasznosnak:

  • Egy globális leállítási esemény, Manual Reset, a szolgáltatás életciklusának korai szakaszában létrehozva, későn felszabadítva.
  • Ne használj Sleep-et a Worker-ciklusokban Stop-képes alternatíva nélkül (várakozás timeout-tal).
  • Minden külső hívás timeout-tal (DB, HTTP, fájlkiszolgálók). Válaszd a timeoutokat úgy, hogy illeszkedjenek a leállítási időkorlátodba.
  • Leállítási időkorlát konfigurálható (pl. INI/Registry), hogy az üzemeltetés reagálhasson újrafordítás nélkül.
  • Fokozatos modell: először graceful (futó feladatok befejezése), majd opcionális „soft abort” (nincsenek új lépések), végül kemény kilépés végső eszközként.
  • Jó leállítási naplók fázisokkal és időméréssel.

Következtetés: TEvent + leállítási időkorlát nem luxus, hanem irányíthatóság

Az elakadt leállítás ritkán egyedi hiba – legtöbbször architekturális hiányosság: munka fut szálakban vagy blokkoló hívásokban, amelyek nem ismerik a közös leállítási jelet. Egy jól definiált leállítási esemény (TEvent, Manual Reset), leállítható várakozások Sleep helyett, következetes timeoutok a külső függőségekre és egy meghatározott leállítási időkorlát mellett olyan szolgáltatást kapsz, amely a mindennapokban előrelátható.

A megoldás különösen megéri, ha a szolgáltatásod éles környezetben, karbantartási ablakokkal, automatizált telepítésekkel vagy kritikus mellékhatásokkal dolgozik. Ilyenkor a „Graceful Shutdown” nem kozmetika, hanem az üzembiztonság építőeleme és kevesebb eszkaláció a következő újraindításnál.

Ha a leállítási útvonalat egyszer tisztán fel akarod építeni, vagy egy meglévő Delphi-szolgáltatást robusztus leállítási logika és üzemeltetési biztonság szempontjából felülvizsgálnál, egy technikai sparring-hívás gyakran a leggyorsabb út a konkrét intézkedésekhez: Lépj kapcsolatba.

Ehhez a témához kapcsolódóan a Delphi Windows szolgáltatás és a Tevent Delphi is fontos. A cikk ezeket az aspektusokat érthetően rendszerezi és bemutatja, mire kell figyelni a gyakorlatban.

Projektet vagy modernizációs feladatot megvitatni a következővel: Net-Base.

Következő lépés

Ha a téma valós projektté válik, az architektúrát, a meglévő rendszert és az üzemeltetést már korán együtt kell értékelni.

Nemcsak egyedi kérdésekben támogatunk, hanem akkor is, amikor forráskódrészletekből, örökölt rendszerekkel kapcsolatos témákból vagy portálötletekből robusztus vállalati projektet kell kialakítani.

  • A jelenlegi állapotot, a célállapotot és a műszaki kockázatokat együttesen értékeljük.
  • REST, az adathozzáférés, a portálok és a Rollout nem kerülnek utólagos teendőkként elhalasztásra.
  • Már korán láthatja, melyik út gazdaságilag és üzemeltetési szempontból életképes.

Bejegyzés megosztása

Ezt a bejegyzést közvetlenül megosztani

LinkedIn, X, XING, Facebook, WhatsApp és e-mail azonnal elérhetők. Instagramhoz linket és rövid szöveget közvetlenül előkészítünk.

E-mail

Az Instagram egy új lapon nyílik meg. A link és a rövid szöveg előzetesen a vágólapra másolódik.