Od teme magazina do projektne prakse
Povezane stranice usluga i tehnologije za članak
Jedan Windows Service in Delphi u svakodnevnoj upotrebi često djeluje nespektakularno: radi u pozadini, obrađuje poslove, zapisuje logove, komunicira s bazama podataka ili REST-API-ima. Dok neko ne klikne „Zaustavi servis“ – ili dok ne dođe do patch-reboota – i servis se ne zaustavi uredno. Tada konzola Usluga prikazuje minute „Wird beendet…“, servis se zaglavi u Stop Pending-statusu, a u najgorem slučaju proces se nasilno prekine. Upravo ovdje se isplati tretirati Windows Service in Delphi Graceful Shutdown kao svjesnu arhitektonsku temu: s jasnim shutdown-signalom, definisanim timeout-ima i threadovima koji zaista reaguju.
U ovom tekstu ne govorimo o internostima frameworka, nego o praktičnom obrascu: TEvent kao stop-signal (kernel-blizak objekat za sinhronizaciju iz System.SyncObjs), kombiniran s Stop-Timeout-strategijom koja uzima u obzir i Windows Service Control Manager (SCM, odnosno Windows-komponentu koja servise starta/zaustavlja) i tvoje vlastite worker-threads. Dodatno tipični rubni slučajevi, pristupi za debagovanje i pitanje kada se ta dodatna logika zaista isplati.
Windows Service in Delphi Graceful Shutdown u praksi
Najčešći razlog je jednostavan: servis ima bar jednu nit koja je zaglavljena u blokirajućoj operaciji i ne zna put za prekinuti izvođenje. Tipični primjeri:
- Polling-petlje s Sleep: „while not Terminated do Sleep(1000)“. Pri zaustavljanju signal stigne, ali nit reaguje tek nakon do jedne sekunde (ili 30 sekundi…).
- Blokirajući I/O: pozivi prema bazi podataka, HTTP-requests, Named Pipes, filesystem-wait–svi elementi koji „jednostavno čekaju“, bez osvrta na stop-signal.
- Queue-consumer bez wakeup: worker čeka na queue, ali pri stop-komandi se ne probudi da izađe.
- Redoslijed lockova / deadlockovi: pri stopu se radi „cleanup“ dok druge niti još drže lockove. To se često javlja samo na putanji zaustavljanja, jer je redoslijed tamo drugačiji nego u normalnom radu.
Windows SCM očekuje da servis brzo odgovori na stop-kommando i kontinuirano prijavljuje svoj status (putem SetServiceStatus; Delphi to enkapsulira u service-komponenti). Ako prihvatiš stop-događaj, ali ne spustiš svoje niti uredno, proces ostaje živ — i Windows će nakon nekog vremena procijeniti da „predugo traje“. Rezultat je ili silno prekidanje ili servis koji zapne u nejasnoj međufazi.
Grundprinzip: Ein Stop-Signal, das jeder Worker versteht
Jedan Graceful Shutdown funkcioniše samo ako imaš signal koji:
- može biti opažen od strane svih relevantnih threads,
- također da djeluje čak i iz blokirajućih stanja čekanja,
- u putanji za zaustavljanje determinističan je (bez „možda će se nekad izvući“-nade),
- jasnu Timeout-strategiju ima.
U Delphi je TEvent za to vrlo korisno sredstvo: objekt događaja koji je interno realiziran preko Windows-Handles (usporedivo s CreateEvent/SetEvent). Možeš ga koristiti kao signal „Stop requested“. Svaki Worker tada ne čeka jednostavno slijepo, već čeka „na posao ili na Stop“.
TEvent richtig wählen: ManualReset vs. AutoReset
Za Stop-signale obično želiš Manual Reset (ručno resetabilan): jednom postavljeno, Event ostaje „signaled“, dok ga ne resetuješ. Time je osigurano da svaki thread koji kasnije uđe u fazu čekanja i dalje prepozna Stop-signal. Auto Reset bi ovdje bio rizičan, jer automatski resetuje signal nakon jednog čekajućeg threada i drugi threadovi bi mogli propustiti Stop-signal.
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).
Važno za arhitekturu:
- OnStop nije mjesto za dugo čekanje bez ažuriranja statusa. To je mjesto gdje pokrećeš gašenje (anstößt) i zatim kontrolisano č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 isti Stop-signal, inače je servis logički zaustavljen, ali fizički još nije gotov.
Sauberes Muster: Stop-Event + Join der Worker + harter Fallback
Praktičan obrazac se sastoji od četiri koraka:
- Zahtjev za zaustavljanje: postaviti Stop-Event, ne prihvaćati nove poslove.
- Pokrenuti wakeups: Ako Worker čekaju na queue-ima ili u Sleep-u, moraju se moći „probuditi“ (npr. preko Event/Queue-signala).
- Uređeno zatvaranje: Worker završavaju svoje petlje, zatvaraju resurse (DB-veze, datoteke, Handles) i javljaju „gotovo“.
- Timeout i fallback: Ako se sve ne završi pravovremeno, moraš donijeti odluku: nastaviti čekati (s ažuriranjem statusa) ili kontrolisano prekinuti/prisilno završiti (ovisno o riziku).
Suština je: nijedan thread ne smije isključivo čekati na tajmer (Sleep) ili isključivo blokirati na I/O, bez paralelnog provjeravanja Stop-signala. Umjesto toga koristiš funkcije čekanja koje uzimaju u obzir više signala (npr. „Stop-Event oder Work-Event“), ili kapsuliraš I/O u timeoute plus provjere Stop-signala.
Stop-Timeout richtig denken: SCM-Timeout vs. eigener Shutdown-Timeout
Ovdje se u projektima događaju najčešći nesporazumi. Postoje dvije različite razine timeouta:
- SCM-očekivanje: Windows očekuje da u statusu SERVICE_STOP_PENDING redovno prijavljuješ napredak. Inače izgleda kao da si zapeo. Delphi se djelomično time bavi, ali čim sam dugo blokiraš, trebaš strategiju kako omogućiti daljnja status- ažuriranja (ili kako zadržati fazu zaustavljanja kratkom).
- Tvoj vlastiti timeout za shutdown: Definišeš npr. „Dajemo si 20 sekundi da uredno završiš tekuće jobove, potom prekidamo.“ To je arhitektonska odluka: konzistentnost podataka nasuprot prisilnom rebootu i operativnim zahtjevima.
U praksi to znači: tvoj servis bi trebao brzo ući u stanje u kojem ne pokreće više novih radnih jedinica i zatim samo čeka da se tekući rad završi – ali ne beskonačno. I ta faza čekanja treba teći u malim intervalima, kako bi mogao reagirati i po potrebi logovati.
Koliko dugo zaustavljanje smije trajati?
Ne postoji magičan broj koji uvijek odgovara. Za mnoge poslovne servise realan cilj je raspon od 5–30 sekundi: dovoljno za „in-flight“ podatke, a ipak kratak za prozore za patchiranje. Ako redovno trebaš više vremena, često je to znak da procesuiraš prevelike jedinice odjednom ili da vanjske zavisnosti (DB/HTTP) rade bez timeouta.
Implementacija s TEvent: konstrukcija koja ostaje stabilna u radu
Provjerena konstrukcija u Delphi-servisu izgleda ovako (bez iscrpnog navođenja framework-detalja):
- Jedan Stop-Event (TEvent, Manual Reset), koji se postavi pri stopu.
- Jedan ili više worker-threadova koji u glavnoj petlji redovno provjeravaju Stop.
- Opcionalno Work-Event ili reda zadataka (queue) koji signalizira rad. Workeri tada čekaju na „Work ili Stop”.
- Shutdown-faza u kojoj se workeri „joined“ (tj. čeka se da završe), ali uz timeout.
Bitno nije da li koristiš TThread, omnithreadlibrary ili vlastiti pool, nego da tvoji workeri ne rade „sljepo“. Worker-loop bi strukturno trebao izgledati: čekanje na događaj(e) → rad u malim chunk-ovima → između chunk-ova provjera Stop → uredno oslobađanje resursa.
Zamka: Terminate sam po sebi nije dovoljan
Mnogi Delphi-threadovi se „prekidaju“ pomoću Terminate. To je međutim samo flag. Ako je thread trenutno u blokirajućem API pozivu, ništa se neće desiti odmah. Zato je vlastiti Stop-Event koristan: možeš ga integrisati u čekajuće pozive i ciljano izazvati wakeup.
Zamka: FreeOnTerminate u kontekstu servisa
U servisima se često vidi FreeOnTerminate := True. To može funkcionisati, ali otežava kontrolu shutdown-a jer često ostaneš bez čiste reference za čekanje na kraj threada i bilježenje grešaka. Za kontrolisanu logiku zaustavljanja obično je stabilnije eksplicitno držati vlasništvo nad threadovima i deterministički ih u shutdown-u čekati i osloboditi.
Blokirajuće operacije: kako ih učiniti stop- sposobnim
Težina nije u samom Eventu, već u mestima gde se tvoj servis blokira. Tri tipične klase:
1) Sleep/Polling ersetzen: Wait mit Stop-Event
Ako radiš periodično („provjera svake 10 sekundi“), nemoj koristiti Sleep(10000), nego čekaj na Event s timeoutom. Tada može tvoje Stop-Event odmah prekinuti čekanje. To smanjuje Stop-Latenz i sprečava utisak „Service reagiert nicht“.
2) Queue-Consumer: Work-Event + Stop-Event kombinieren
Ako imaš Producer/Consumer arhitekturu (npr. poslovi se stavljaju u red), treba ti signal koji će probuditi consumere. Često je to dodatni TEvent (Work available). Consumer zatim čeka na dvije handles: „Work“ ili „Stop“. Pri Stop-u postaviš Stop-Event i po potrebi i Work-Event, tako da svi Consumer-i sigurno izađu iz čekanja.
3) Externe Calls (DB/HTTP): Timeouts und Abbruchpfade
Kod pristupa bazi podataka ili HTTP poziva odlučuje se hoće li tvoj servis uredno prestati. Za rad vrijedi: Kein Call ohne Timeout. Timeout nije luksuz, već preduvjet za upravljivost. Dodatno bi trebao između Retries/Backoff-Phasen uvijek provjeravati Stop. Inače dobiješ klasičan slučaj: „Service stoppt nicht, weil er gerade 10 Retries mit Sleep macht“.
Kod nekih biblioteka možeš eksplicitno pokrenuti prekide (npr. Query-Abbruch). Ako to nije moguće, moraš barem konfigurirati timeout-e dovoljno kratkim da ne probiju den Shutdown-Timeout.
Stop Pending korrekt: Status, Logging und Erwartungsmanagement
Kada se servis zaustavlja, iz operativnog ugla važno je razumjeti, gdje zapinje. Za to ti trebaju dvije stvari:
- Log-Marker u Stop-pfadu: „Stop zatražen“, „nema novih poslova“, „čeka na Worker“, „Worker X završen“, „Shutdown fertig“.
- Messbare Zeiten: Koliko dugo traje Stop? Koja faza troši vrijeme? Često je dovoljno monotono mjernište poput GetTickCount64 ili TStopwatch (monotono = nije izobličeno promjenama sistemskog vremena).
Ako u Stop-putanji zabilježiš samo jedan log unos „Stopping…“, debugovanje na terenu ostaje pogađanje. U radu servisa logovi su često jedino što dobiješ bez interakcije.
Koji su Logovi u servisima zaista korisni?
- Service-PID, vrijeme pokretanja, verzija/Build (bez pretjeranog Overhead).
- Broj aktivnih Worker-a, broj in-flight Jobs.
- Aktivne vanjske zavisnosti: „DB-Call läuft“, „HTTP-Request läuft“, „Datei-Flush läuft“ (samo agregirano, ne svaki detalj).
- Stop-Timeout dostignut: koji Worker-i su još aktivni?
Debugovanje na terenu: reproducibilno umjesto nagađanja
Problemi sa zaustavljanjem često se pojavljuju samo u produkciji: drugačije opterećenje, druga latencija, drugačija prava, drugi vremenski prozori za Patch. Nekoliko praktično isprobanih poluga:
Testirati servis pod kontrolom
- Stop tokom aktivne obrade (ne dok je u idle).
- Stop tokom vanjske smetnje: DB kratko nedostupna, HTTP-endpoint spor, Fileshare nedostupan.
- Stop neposredno nakon starta (Race-Conditions: Worker još u izgradnji).
Event Viewer und Service Control Manager Signale
Windows zapisuje događaje servisa, ali oni su često grubi. Bolje je ako tvoj servis sam zapisuje u log-datoteku ili u Windows Event Log. Bitno je: logovanje bi trebalo još funkcionisati u Stop-putanji. Ako prerano oslobodiš logger tokom shutdown-a ili flush blokira, izgubit ćeš upravo one ključne tragove.
Uočavanje zaglavljenih niti
Ako često vidiš „Stop Timeout“, vrijedi pogledati stanja niti (npr. putem Debugger/Procdump u testnom okruženju). Često nađeš nit u stanju čekanja na handle koji nikad nije signaliziran, ili u mrežnom pozivu bez timeout-a. Rješenje tada rijetko znači „još više Sleep-a“, već čist put prekida.
Kada se trud zaista isplati?
Minimalistički servis koji ima samo timer i nema vanjske zavisnosti ponekad može „einfach stoppen“. Međutim, čim se bilo koje od sljedećeg odnosi na tvoj servis, uredan Graceful Shutdown se gotovo uvijek isplati:
- Servis obrađuje Jobs mit Seiteneffekten (pisanje fajlova, DB-transakcije, API-pozivi).
- Postoji više niti ili pool.
- Servis ovisi o mrežnim resursima (DB, REST, Message Broker, Fileshares).
- Operativno osoblje zahtijeva planirane prozore održavanja (Reboots, Updates, Failover).
Vrijednost nije „Eleganz“, već sigurnost u radu: manje grubih prekida procesa, manje nekonzistentnih međustanja, manje ručnih intervencija.
Praktične zamke: šta pri shutdown-u često pođe po zlu
1) Stop je postavljen, ali novi Jobs i dalje dolaze
Ako prihvataš dolazni rad (npr. preko Socket, Datei-Trigger, Timer), moraš u Stop-putanji prvo zaustaviti prihvatanje nove posla: zatvoriti Listener, deaktivirati Timere, zaustaviti Scheduler. Inače ćeš trčati za krajem jer stalno počinju novi Jobs.
2) Cleanup blokira (Flush, Close, Finalize)
„Samo još brzo sve flushen“ može u kontekstu servisa biti opasno ako meta (mrežni disk, Remote-Log, DB) trenutno visi. Zato: Cleanup da, ali sa ograničenim vremenom. U krajnjem slučaju moraš odlučiti koje podatke gubiš u memoriji, umjesto da blokiraš kompletan Stop.
3) Zaključavanja i redoslijed
Pri Stopu često pristupaš istim strukturama podataka kao Worker-i (Queues, Caches, States). Ako Stop-thread drži Locks i zatim čeka završetak Worker-a, dok ti Worker-i trebaju isti Lock, nastaje Stop-Deadlock. Protivmjere: držati vremena držanja Lock-ova kratkim, u Stop-putu ne čekati „pod Lock-om“, definirati jasan redoslijed.
4) Istovremenost pri dvostrukom Stopu
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 sigurno zaštićena (npr. preko atomskog flag-a).
Operativni pogled: Šta administratori i IT-leadovi očekuju od servisa
Za operacije i administraciju na kraju nije presudno koliko je „lijep“ kod, nego da li servis:
- pri Stop pouzdano završava (planirano, bez zastoja),
- pri Stop ne proizvodi nekonzistentne podatke (npr. nepotpune datoteke, otvorene transakcije),
- u slučaju greške daje upotrebljive logove,
- prilikom prozora održavanja i deploy-ova je predvidljiv.
To je i razlog zašto tema Stop-Timeout nije samo „stvar developera“: utječe na cikluse patchanja, vremena oporavka i pitanje da li su automatizirani deploy-ovi uopće mogući.
Konkretne smjernice za robusni dizajn gašenja
Ako želiš pragmatično standardizirati temu, ove smjernice su se pokazale korisnima:
- Globalni Stop-Event, Manual Reset, kreiran rano u životnom ciklusu servisa, oslobađan kasno.
- Bez Sleep u Worker-loopovima bez alternative koja podržava Stop (Wait s timeout-om).
- Svi eksterni pozivi s timeoutima (DB, HTTP, Fileshares). Timeout-e birati tako da se uklope u tvoj Shutdown-Timeout.
- Stop-Timeout kao konfiguracija (npr. u INI/Registry), kako bi operacije mogle reagirati bez ponovnog kompiliranja.
- Model nivoa: prvo uredno (graceful) — završetak tekućih zadataka, zatim opcionalni „soft abort“ (ne započinjati nove korake), na kraju čvrst Exit kao posljednje rješenje.
- Dobri Stop-Logovi s fazama i mjerenjem vremena.
Zaključak: TEvent + Stop-Timeout nije luksuz, nego mogućnost upravljanja
Zaglavljeni Stop rijetko je izolovana greška – obično je to rupa u arhitekturi: posao radi u threadovima ili blokirajućim pozivima koji ne poznaju zajednički Stop-signal. S jasnim Stop-eventom (TEvent, Manual Reset), waitovima koji podržavaju Stop umjesto Sleep-a, dosljednim timeoutima za eksterne ovisnosti i definiranim Shutdown-Timeout-om dobiješ servis koji je u svakodnevnom radu predvidljiv.
Kod se posebno isplati ako tvoj servis radi u produkcijskim okruženjima s prozorima održavanja, automatiziranim deploy-ovima ili kritičnim nuspojavama. Tada „Graceful Shutdown“ nije kozmetika, nego komponenta za stabilan rad i manje eskalacija pri sljedećem rebootu.
Ako želite pravilno postaviti vaš Stop-put ili provjeriti postojeći Delphi-servis u pogledu robusne shutdown-logike i sigurnosti operacija, tehnički sparrings-call često je najbrži put do jasnih mjera: Kontaktirajte nas.
Za ovu temu su također važni Delphi Windows Service i Tevent Delphi. Članak razjašnjava te aspekte i pokazuje na šta treba paziti u praksi.
Razgovarajte o projektu ili modernizacijskom zahvatu s Net-Base.
Sljedeći korak
Kada se tema pretvori u stvarni projekat, arhitektura, postojeći sistem i operacije trebaju se rano sagledati zajedno.
Pružamo podršku ne samo pri pojedinačnim pitanjima, već i kada iz fragmenata izvornog koda, naslijeđenih sistema ili ideja za portal treba nastati robustan poslovni projekat.
- Postojeće stanje, ciljno stanje i tehnički rizici procjenjuju se zajedno.
- REST, pristup podacima, portali i Rollout se ne odgađaju kao naknadne posljedice.
- Vi rano vidite koji je put ekonomski i operativno održiv.