Net-Base Časopis

02.08.2026

Windows Service u Delphi: Uredno zaustavljanje s TEvent i Stop-Timeout ispravno implementirati

Kad se Windows-servis 'zaglavi' pri zaustavljanju, rijetko je slučajnost: najčešće blokiraju threadovi, I/O ili Sleep-petlje bez puta prekida. Ovaj praktični članak pokazuje kako u Delphi pomoću TEvent implementirati uredno gašenje (graceful shutdown) i ispravno rukovati Stop-timeoutima...

02.08.2026

Od teme magazina do projektne prakse

Povezane stranice usluga i tehnologije za članak

Jedan Windows Service in Delphi u svakodnevici često djeluje nespektakularno: radi u pozadini, obrađuje poslove, zapisuje logove, komunicira s bazama podataka ili REST-API-jevima. Sve dok netko ne klikne „Dienst beenden“ – ili dok ne dođe vrijeme za patch-reboot – a servis se ne zaustavi uredno. Tada konzola servisa minute prikazuje „Wird beendet…“, servis zapne u Stop Pending-statusu, a u najgorem slučaju proces se brutalno prekine. Upravo zato vrijedi Windows Service in Delphi Graceful Shutdown tretirati kao svjesnu arhitektonsku temu: s jasnim shutdown-signalom, definiranim timeoutima i threadovima koji zaista reagiraju.

U ovom članku ne radi se o internim dijelovima frameworka, nego o praktičnom obrascu: TEvent kao stop-signal (kernel-natjecirano sinhronizacijsko objekt iz System.SyncObjs), u kombinaciji sa Stop-Timeout-strategijom koja uzima u obzir i Windows Service Control Manager (SCM, odnosno komponentu Windows koja pokreće/zaustavlja servise) i tvoje vlastite worker-niti. Uz to tipični rubni slučajevi, pristupi za debugiranje i pitanje kada se dodatna logika stvarno isplati.

Windows Service in Delphi Graceful Shutdown u praksi

Najčešći uzrok je jednostavan: servis ima barem jednu nit koja je zaglavljena u blokirajućoj operaciji i nema put za prekid. Tipični primjeri:

  • Polling-petlje sa Sleep: „while not Terminated do Sleep(1000)“. Pri zaustavljanju signal stigne, ali nit reagira tek nakon do 1 sekunde (ili 30 sekundi…).
  • Blokirajući I/O: pozivi bazi podataka, HTTP-zahtjevi, Named Pipes, čekanja na datotečni sustav – sve što ‚jednostavno čeka‘, bez obraćanja pažnje na stop-signal.
  • Queue-consumer bez wakeup: worker čeka na red (queue), ali pri zaustavljanju nije probuđen da izađe.
  • Redoslijed lockova / deadlockovi: pri zaustavljanju se radi ‚cleanup‘, dok druge niti još drže lockove. To se često pojavljuje samo u putu zaustavljanja, jer je tamo redoslijed drugačiji nego u normalnom radu.

Windows SCM očekuje da servis brzo odgovori na naredbu za zaustavljanje i kontinuirano prijavljuje svoj status (preko SetServiceStatus; Delphi to enkapsulira u komponenti servisa). Ako prihvatiš stop-događaj, ali niti ne ugasiš ispravno, proces ostaje živ – i Windows će nakon nekog vremena odlučiti da to ‚predugo traje‘. Posljedica je ili grub prekid ili servis koji zapne u nejasnom međustanju.

Osnovno načelo: Ein Stop-Signal, das jeder Worker versteht

Apstraktna grafika: Worker-niti čekaju na posao ili Stop-događaj i uredno završavaju
Ako workeri čekaju na „Work oder Stop“, smanjuje se stop-latencija bez pollinga.

Graceful Shutdown funkcionira samo ako imaš signal koji:

  • da ga sve relevantne niti mogu detektirati,
  • da djeluje i iz blokirajućih stanja čekanja izlazi,
  • na putu zaustavljanja determinističan je (nema nade „možda će jednom izaći“),
  • ima jasnu Timeout-Strategie.

U Delphi je TEvent za to vrlo koristan alat: objekt događaja koji se interno realizira preko Windows-handles (usporedivo s CreateEvent/SetEvent). Možete ga koristiti kao signal „Stop requested“. Svaki Worker tada ne čeka slijepo, nego čeka „na posao ili na Stop“.

TEvent richtig wählen: ManualReset vs. AutoReset

Kod Stop-signala obično želite Manual Reset (ručno vraćanje): jednom postavljeno, događaj ostaje „signaled“ dok ga ne poništite. Time je zajamčeno da će svaki thread koji kasnije uđe u fazu čekanja ipak prepoznati Stop-signal. Auto Reset bi ovdje bio rizičan, jer automatski poništi signal nakon jednog čekajućeg threada i drugi threadovi bi mogli propustiti Stop-signal.

Delphi-Service-Lebenszyklus: Wo Stop wirklich ankommt

Jedan Delphi-Windows- i Linux-Services obično se temelji na TService (VCL/RTL). SCM šalje komande (Start, Stop, Pause, Continue). Delphi potom poziva odgovarajuće evente/metode (ovisno o predlošku, npr. OnStart, OnStop, OnExecute).

Važno za arhitekturu:

  • OnStop nije mjesto za dugotrajno čekanje bez ažuriranja statusa. To je mjesto gdje pokrećeš gašenje i zatim kontrolirano čekaš – s timeoutom.
  • OnExecute je često petlja. Ako tamo radiš „beskrajno“, petlja mora reagirati na Stop-signal.
  • Worker-Threads (TThread ili Thread-poolovi) moraju reagirati na isto Stop-signal, inače je servis logički zaustavljen, ali fizički još nije dovršen.

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

Praktični obrazac sastoji se od četiri koraka:

  1. Stop anfordern: postaviti Stop-Event, ne prihvaćati nove zadatke.
  2. Wakeups auslösen: ako Workeri čekaju na redovima ili su u Sleepu, moraju se moći „probuditi“ (npr. preko Event/Queue signala).
  3. Geordnet beenden: Workeri završavaju svoje petlje, zatvaraju resurse (DB-veze, datoteke, handles) i prijavljuju „gotovo“.
  4. Timeout und Fallback: ako se sve ne završi na vrijeme, morate donijeti odluku: nastaviti čekati (s ažuriranjem statusa) ili kontrolirano prekinuti/tvrdo zaustaviti (ovisno o riziku).

Suština je: nijedan Thread ne smije isključivo čekati na vrijeme (Sleep) ili isključivo blokirati na I/O, bez paralelnog uvažavanja Stop-signala. Umjesto toga koristite funkcije čekanja koje uzimaju u obzir više signala (npr. „Stop-Event ili Work-Event“), ili kapsulirate I/O u timeout-e plus provjere Stop-signala.

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

Ovdje u projektima nastaje većina nesporazuma. Postoje dva različita nivoa timeouta:

  • Očekivanje SCM-a: Windows očekuje da u statusu SERVICE_STOP_PENDING redovito prijavljuješ napredak. Inače izgleda kao da visiš. Delphi se djelomično time bavi, ali čim si sam dulje blokiran, treba ti strategija kako omogućiti daljnja ažuriranja statusa (ili kako držati svoju fazu zaustavljanja kratkom).
  • Tvoj vlastiti timeout za shutdown: Definiraš, npr., „Dajemo si 20 sekundi da uredno dovršimo tekuće poslove, zatim prekidamo.“ To je arhitekturna odluka: konzistentnost podataka nasuprot prisili na reboot nasuprot operativnim zahtjevima.

Praktično to znači: tvoj Service trebao bi brzo prijeći u stanje u kojem on ne pokreće više novih jedinica rada, i onda samo čeka da tekući rad završi – ali ne beskonačno. I ta faza čekanja treba se izvoditi u malim intervalima, da bi mogao reagirati i po potrebi logirati.

Koliko dugo smije trajati zaustavljanje?

Ne postoji magičan broj koji uvijek odgovara. Za mnoge poslovne servise ciljano područje od 5–30 sekundi je realistično: dovoljno vremena za „in-flight“ podatke, ali dovoljno kratko za patch-prozore. Ako redovito trebaš dulje, to često upućuje na to da obrađuješ prevelike jedinice odjednom ili da vanjske ovisnosti (DB/HTTP) rade bez timeouta.

Implementacija mit TEvent: Aufbau, der im Betrieb stabil bleibt

Provjerena arhitektura u Delphi-Service izgleda ovako (bez ulaska u detalje frameworka):

  • Jedan Stop-Event (TEvent, Manual Reset) koji se postavlja pri Stop-u.
  • Jedna ili više radnih niti koje u svojoj glavnoj petlji redovito provjeravaju Stop.
  • Opcionalno jedno Work-Event ili red (queue) koji signalizira rad. Radne niti tada čekaju na „Work ili Stop“.
  • Faza Shutdown koja radne niti „join-a“ (tj. čeka dok se ne završe), ali s timeoutom.

Bitno nije koristiš li TThread, omnithreadlibrary ili vlastiti pool, već da tvoje radne niti ne rade „sljepo“. Worker-loop bi strukturno trebao izgledati ovako: čekanje na događaj(e) → rad u malim cjelinama → između cjelina provjera Stop → uredno oslobađanje resursa.

Zamka: Terminate sam po sebi nije dovoljan

Mnoge Delphi-niti se s Terminate „prekidaju“. To je međutim samo flag. Ako je nit trenutno u blokirajućem API pozivu, najprije se neće dogoditi ništa. Zato je vlastiti Stop-Event koristan: možeš ga integrirati u pozive čekanja i ciljano izazvati buđenja.

Zamka: FreeOnTerminate u kontekstu servisa

U servisima se često vidi FreeOnTerminate := True. To može funkcionirati, ali otežava kontrolu shutdown-a, jer često više nemaš čistu referencu da bi čekao kraj niti i evidentirao greške. Za kontroliranu logiku zaustavljanja obično je stabilnije eksplicitno posjedovati niti i u shutdown-u deterministički ih čekati i osloboditi.

Blokirajuće operacije: kako ih učiniti stop-fähig

Scena za otklanjanje poteškoća: mrežna veza kao uzrok blokirajućih poziva i timeouta
Blokirajući I/O bez timeouta je najčešći razlog za Stop-zastoje u servisu.

Složeni dio nije sam event, nego mjesta na kojima se vaš servis blokira. Tri tipične klase:

1) Sleep/Polling zamijeniti: Wait s Stop-Eventom

Ako vaš proces radi periodično („provjera svake 10 sekundi“), nemojte koristiti Sleep(10000), nego čekajte na event s timeoutom. Tako vaš Stop-Event može odmah prekinuti čekanje. To smanjuje latenciju za zaustavljanje i sprječava dojam „servis ne reagira“.

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

Ako imate Producer/Consumer arhitekturu (npr. zadaci se stavljaju u queue), trebate signal koji budi consumere. Često je to dodatni TEvent (Work available). Consumer zatim čeka na dva handlea: „Work“ ili „Stop“. Kod zaustavljanja postavite Stop-Event i, po potrebi, i Work-Event, kako bi svi consumeri garantirano izašli iz čekanja.

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

Pri pristupima bazi podataka ili HTTP pozivima odlučuje se hoće li vaš servis uredno stati. Za rad vrijedi: Nijedan poziv bez timeouta. Timeout nije luksuz nego preduvjet za upravljivost. Dodatno biste između retry faza/backoff-a uvijek trebali provjeriti Stop. Inače dobijete klasik: „Servis se ne zaustavlja jer upravo radi 10 retrija uz Sleep“.

U nekim bibliotekama možete eksplicitno pokrenuti prekide (npr. prekid upita). Ako to nije moguće, morate barem konfigurirati timeoutove dovoljno kratko da ne prekorače timeout za gašenje.

Stop Pending korrekt: Status, Logging und Erwartungsmanagement

Mjerenje vremena i analiza logova za dijagnostiku Stop-timeoutova kod Windows-servisa
Fazni logovi i mjerenje vremena čine Stop-timeoutove reproducibilnima i objašnjivima.

Kada se servis zaustavlja, iz perspektive pogona važno je razumjeti gdje zapinje. Za to trebate dvije stvari:

  • Log-marker u putu zaustavljanja: „Stop zatražen“, „nema novih poslova“, „čekam radnike“, „Worker X završen“, „gašenje završeno“.
  • Mjerljiva vremena: Koliko dugo traje zaustavljanje? Koja faza troši vrijeme? Često je dovoljna monotona mjera vremena kao GetTickCount64 ili TStopwatch (monotono = nije iskrivljeno promjenama sistemskog vremena).

Ako u putu za zaustavljanje zapišeš samo jedan jedini log unos „Stopping…“, debugiranje na terenu postaje pogađanje. U radu kao servis logovi su često jedino što dobiješ bez interakcije.

Koji su logovi u servisima zaista korisni?

  • PID servisa, vrijeme pokretanja, verzija/build (bez pretjeranog overheada).
  • Broj aktivnih worker-a, broj poslova u obradi.
  • Aktivne vanjske ovisnosti: „DB-poziv u tijeku“, „HTTP-zahtjev u tijeku“, „file-flush u tijeku“ (samo agregirano, ne svaki detalj).
  • Dosegnut Stop-Timeout: koji su worker-i još otvoreni?

Debugiranje u produkciji: reproducirati umjesto nagađati

Problemi sa zaustavljanjem se rado pojavljuju samo u produkciji: druga opterećenja, druge latencije, druga prava, druga vremenska okna za patch. Nekoliko praktično provjerenih poluga:

Testirati servis pod kontrolom

  • Zaustavljanje tijekom aktivne obrade (ne u idle stanju).
  • Zaustavljanje tijekom vanjske smetnje: DB kratko nedostupna, HTTP-endpoint spor, fileshare nedostupan.
  • Zaustavljanje neposredno nakon pokretanja (race-conditions: worker-i još u postavljanju).

Event Viewer i signali Service Control Managera

Windows piše događaje servisa, ali oni su često grubi. Bolje je kad tvoj servis sam piše u log-datoteku ili u Windows Event Log. Važno je pritom: logiranje treba još raditi u putu za zaustavljanje. Ako oslobađaš logger-e previše rano tijekom shutdowna ili je flush blokiran, izgubit ćeš upravo one odlučujuće tragove.

Učiniti zaglavljene niti vidljivima

Ako opetovano vidiš „Stop Timeout“, vrijedi pogledati stanja niti (npr. preko Debugger-a/Procdump-a u testnom okruženju). Često ćeš naći nit u wait-stanju na handleu koji nikad nije signalan, ili u mrežnom pozivu bez timeouta. Rješenje tada rijetko znači „još više Sleep-a“, nego jasan put za prekid.

Kada se trud doista isplati?

Minimalistički servis koji ima samo timer i nema vanjske ovisnosti ponekad se može „jednostavno zaustaviti“. Međutim, čim vrijedi barem jedna od sljedećih točaka, uredan Graceful Shutdown se gotovo uvijek isplati:

  • Servis obrađuje poslove sa nuspojavama (pisanje datoteka, DB-transakcije, API-pozivi).
  • Postoji više niti ili pool.
  • Servis ovisi o mrežnim resursima (DB, REST, message broker, fileshares).
  • Operacija zahtijeva planirana maintenance prozore (rebooti, update-i, failover).

Dobitak nije „elegancija“, nego sigurnost u radu: manje grubih prekida procesa, manje inkonzistentnih međustanja, manje ručnih intervencija.

Praktične zamke: što pri shutdownu rado pođe po zlu

1) Stop je postavljen, ali novi poslovi i dalje dolaze

Ako primaš dolazni rad (npr. preko socket-a, file-triggera, timera), moraš u putu za zaustavljanje prvo zaustaviti prihvaćanje nove obrade: zatvoriti listener-e, deaktivirati timere, zaustaviti scheduler. Inače ćeš trčati za krajem jer se stalno pokreću novi poslovi.

2) Cleanup blokira (Flush, Close, Finalize)

„Samo još sve flushati“ može biti opasno u kontekstu servisa ako je cilj (mrežni disk, remote-log, DB) trenutno zapeo. Zato: cleanup da, ali s ograničenim vremenom. U krajnjem slučaju moraš odlučiti koja seća ćeš izgubiti u memoriji, umjesto da blokiraš kompletno zaustavljanje.

3) Locks und Reihenfolge

Pri Stopu često pristupaš istim strukturama podataka kao i Worker-i (Queues, Caches, States). Ako Stop-thread drži lockove i potom čeka završetak Worker-a, dok Worker-i trebaju iste lockove, nastaje Stop-deadlock. Protumjere: držati vremena držanja lockova kratkima, u Stop-putu ne čekati „pod lockom“, definirati jasan poredak.

4) Konkurentnost kod dvostrukog Stopa

U praksi se Stop može pokrenuti više puta (npr. Stop + Shutdown, ili Stop se ponovo pojavi). Tvoj Stop-put treba biti idempotentan: postavljanje Stop-eventa je u redu, ali dvostruka Join/Free-logika mora biti čvrsto zaštićena (npr. preko atomske zastavice).

Operativni pogled: Što administratori i IT-voditelji očekuju od servisa

Za operacije i administraciju na kraju nije presudno koliko je „lijep“ kod, nego hoće li servis:

  • pri Stopu pouzdano završava (planirano, bez zastoja),
  • pri Stopu ne proizvodi nekonzistentne podatke (npr. polovične datoteke, otvorene transakcije),
  • u slučaju greške daje korisne logove,
  • u održavanju i deploymentima je predvidljiv.

To je također razlog zašto tema Stop-Timeout nije samo „posao programera“: utječe na cikluse patchiranja, vremena oporavka i pitanje jesu li automatizirani deploymenti uopće mogući.

Konkrektne smjernice za robustan dizajn gašenja

Ako želiš pragmatično standardizirati temu, pokazale su se sljedeće smjernice:

  • Globalni Stop-event, Manual Reset, kreiran rano u životnom ciklusu servisa, oslobađa se kasno.
  • Bez Sleep u Worker-petljama bez alternative koja podržava Stop (Wait s timeoutom).
  • Svi vanjski pozivi s timeoutima (DB, HTTP, Fileshares). Timeoute odabrati tako da stanu u tvoj shutdown-timeout.
  • Stop-timeout kao konfiguracija (npr. u INI/Registry), tako da operacije mogu reagirati bez ponovnog kompajliranja.
  • Model u fazama: prvo graceful (postojeći jobovi dovršavaju), zatim opcionalni „soft abort“ (nema novih koraka), pa kao zadnje sredstvo hard exit.
  • Dobri Stop-logovi s fazama i mjerenjem vremena.

Zaključak: TEvent + Stop-Timeout nije luksuz, nego upravljivost

Zaglavljeni Stop rijetko je pojedinačna greška – obično je to arhitekturni propust: rad se odvija u threadovima ili blokirajućim pozivima koji ne poznaju zajednički Stop-signal. S jasnim Stop-eventom (TEvent, Manual Reset), stop-funkcionalnim Waitovima umjesto Sleepa, dosljednim timeoutima za vanjske ovisnosti i definiranim shutdown-timeoutom dobiješ servis koji je u praksi predvidljiv.

Kod se posebno isplati ako tvoj servis radi u produkcijskim okruženjima s prozorima održavanja, automatiziranim deploymentima ili kritičnim nusposljedicama. Tada je „Graceful Shutdown“ nije kozmetika, nego gradivni element za stabilan rad i manje eskalacija pri sljedećem rebootu.

Ako želite jednom uredno postaviti vaš Stop-put ili provjeriti postojeći Delphi-servis u pogledu robusne shutdown-logike i sigurnosti rada, tehnički sparrings-call često je najbrži put do jasnih mjera: kontaktirajte nas.

Za ovu temu su također važni Delphi Windows servis i Tevent Delphi. Članak smješta ove aspekte razumljivo i pokazuje na što se u praksi treba fokusirati.

Razgovarajte o projektu ili modernizacijskom pothvatu s Net-Base.

sljedeći korak

Ako se tema pretvori u stvarni projekt, arhitekturu, postojeće sustave i operacije trebalo bi rano zajednički razmotriti.

Podržavamo vas ne samo u pojedinačnim pitanjima, već i kada iz isječaka izvornog koda, naslijeđenih sustava ili ideja za portale treba nastati pouzdan poslovni projekt.

  • Postojeće stanje, ciljna slika i tehnički rizici procjenjuju se zajedno.
  • REST, pristup podacima, portali i rollout neće biti odgođeni kao naknadne posljedice.
  • Rano prepoznajete koji je put ekonomski i operativno održiv.

Podijeli objavu

Izravno proslijedite ovu objavu

LinkedIn, X, XING, Facebook, WhatsApp i e-pošta su odmah dostupni. Za Instagram odmah pripremamo poveznicu i kratak tekst.

E-pošta

Instagram se otvara u novoj kartici. Link i kratki tekst se prethodno kopiraju u međuspremnik.