Net-Base Magasin

02.08.2026

Windows-teneste i Delphi: Riktig implementering av Graceful Shutdown med TEvent og Stop-Timeout

Når ein Windows-teneste heng seg opp ved stopping, er det sjeldan tilfeldig: vanlegvis blokkerer trådar, I/O eller sleep-løkker utan avbrotveg. Denne praksisartikkelen viser korleis du i Delphi med TEvent implementerer ein ryddig, kontrollert nedstenging og handsamar stopp-tidsavgrensingar korrekt.

02.08.2026

Frå magasinetema til prosjektpraksis

Passande teneste- og tekniske sider til innlegget

Ein Windows Service in Delphi verkar ofte lite spektakulær i kvardagen: køyrer i bakgrunnen, handterer jobbar, skriv loggar, snakkar med databasar eller REST-APIs. Til nokon klikkar „Stopp tenesta“ – eller ein patch-reboot står for tur – og tenesta stoppar ikkje reint. Då viser tenestekonsollen minuttvis «Wird beendet…», tenesta heng i Stop Pending-status, og i verste fall blir prosessen tvangsavslutta. Nett her løyner det seg å handsame Windows Service in Delphi Graceful Shutdown som eit medvite arkitekturtema: med eit klart shutdown-signal, definerte timeouts og trådar som verkeleg reagerer.

I denne artikkelen handlar det ikkje om interne detaljar i rammeverket, men om eit praktisk mønster: TEvent som stoppsignal (eit kjernenært synkroniseringsobjekt frå System.SyncObjs), kombinert med ei Stop-Timeout-strategi som tek omsyn både til Windows Service Control Manager (SCM, altså den Windows-komponenten som startar/stoppar tenester) og dine eigne worker-trådar. I tillegg typiske randtilfelle, debugging-tilnærmingar og spørsmålet om når den ekstra logikken verkeleg løner seg.

Windows Service in Delphi Graceful Shutdown i praksis

Den vanlegaste årsaka er enkel: tenesta har minst éin tråd som sit i ei blokkerande operasjon og kjenner ingen avbrotveg. Klassikarar:

  • Polling-sløyfer med Sleep: „while not Terminated do Sleep(1000)“. Ved stopp kjem signalet, men tråden reagerer først etter opptil 1 sekund (eller 30 sekund…).
  • Blokkerande I/O: databasekall, HTTP-forespurnader, Named Pipes, filesystem-waitar – alt som berre ventar utan å sjå etter eit stoppsignal.
  • Queue-consumer utan wakeup: Ein worker ventar på ei kø, men ved stopp blir han ikkje vekka for å avslutte.
  • Låsrekkjefylgje/Deadlocks: Ved stopp blir «Cleanup» kjørt medan andre trådar framleis held lås. Dette skjer gjerne berre i stoppløypa, fordi rekkjefølgja der er annleis enn i normaldrift.

Den Windows SCM forventar at ein teneste reagerer raskt på eit stopp-kommando og løpande rapporterer statusen (via SetServiceStatus; Delphi kapslar dette i service-komponenten). Dersom du aksepterer eit stopp-hending, men ikkje køyrer ned trådane dine reint, held prosessen fram med å vere levande – og Windows avgjer etter ei stund at det «tar for lang tid». Resultatet er anten ein hard avbrot eller ei teneste som sit fast i eit uklart mellomstadium.

Grundprinzip: Ein Stop-Signal, das jeder Worker versteht

Abstrakt grafikk: Worker-trådar ventar på arbeid eller stoppe-hending og avsluttar på ordna vis
Når worker ventar på „Work eller Stop“, minkar stopp-latensen utan polling.

Ein Graceful Shutdown fungerer berre dersom du har eit signal som:

  • som alle relevante trådar kan observere,
  • også frå blokkerande ventetilstandar har effekt,
  • i Stop-stien deterministisk er (inga „kanskje kjem han ut ein gong“-håp),
  • ein klar Timeout-Strategie har.

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-forventning: Windows forventar at du i status SERVICE_STOP_PENDING regelmessig melder framgang. Ellers verkar det som om du heng. Delphi tek delvis hand om dette, men straks du sjølv blokkerer over lengre tid, treng du ein strategi for korleis du vidare kan levere statusoppdateringar (eller korleis du held stoppfasen kort).
  • Din eigne shutdown-timeout: Du definerer t.d. «Vi gir oss 20 sekund for å avslutte pågåande jobbar ordentleg, så avbryt vi.» Dette er eit arkitekturval: datakonsistens vs. tvungen omstart vs. driftskrav.
  • I praksis betyr det: Tenesta di bør raskt kome i ein tilstand der ho ikkje startar fleire nye arbeidseiningar, og deretter berre ventar på at pågåande arbeid skal bli ferdig – men ikkje i det uendelege. Og denne ventefasen bør gå i små intervall, slik at du kan reagere og eventuelt logge.

    Kor lenge kan stoppet vare?

    Det finst ikkje noko magisk tal som alltid passar. For mange business-tenester er eit målområde på 5–30 sekund realistisk: nok tid for «in-flight» data, men kort nok for patch-vindauge. Dersom du jamleg treng lengre tid, er det ofte ei indikasjon på at du behandlar for store einingar om gongen eller at eksterne avhengigheiter (DB/HTTP) køyrer utan timeout.

    Implementering med TEvent: Oppbygging som held seg stabil i drift

    Ein vellukka oppbygging i Delphi-tenesta ser slik ut (utan å dra ut rammeverksdetaljar):

    • Eit Stop-Event (TEvent, Manual Reset) som blir sett ved stopp.
    • Ein eller fleire Worker-Threads som i hovudsløyfa si regelmessig sjekkar for stopp.
    • Valfritt eit Work-Event eller ein kø som signaliserer arbeid. Worker ventar då på «Work eller Stop».
    • Ei Shutdown-fase som «joined» workerane (altså ventar til dei er ferdige), men med timeout.

    Det avgjerande er ikkje om du bruker TThread, omnithreadlibrary eller ein eigen pool, men at Worker-ane dine ikkje går «blindt». Ein Worker-loop bør strukturelt sjå slik ut: Vent på hending(ar) → arbeid i små chunkar → mellom chunkane sjekke stopp → frigje ressursar ryddig.

    Fallgruve: Terminate aleine er ikkje nok

    Mange Delphi-Threads blir med Terminate «avbrotne». Det er likevel berre eit flagg. Dersom tråden sit i ei blokkande API, skjer det i første omgang ingenting. Difor er eit eige Stop-Event så nyttig: Du kan integrere det i ventekall og trigge målretta wakeups.

    Fallgruve: FreeOnTerminate i service-kontekst

    I tenester ser ein ofte FreeOnTerminate := True. Det kan fungere, men gjer nedstenging vanskelegare å kontrollere, fordi du då ofte ikkje har ei rein referanse å vente på for trådslutt eller for å protokollere feiltilstandar. For kontrollert stopplogikk er det som regel meir stabilt å eige Threads eksplisitt og under shutdown deterministisk vente på dei og frigje dei.

    Blokkerande operasjonar: Slik gjer du dei stoppbare

    Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
    Blokkerande I/O utan Timeout er den vanlegaste årsaka til at tenesta heng seg i stopp.

    Den vanskelege delen er ikkje sjølve eventet, men stadene der tenesta di blokkerer. Tre typiske kategoriar:

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

    Hvis du køyrer periodisk arbeid («sjekk kvart 10. sekund»), bruk ikkje Sleep(10000). Vent i staden på eit event med timeout. Då kan Stop-Eventet avslutte ventinga med ein gong. Det reduserer stopp-latens og hindrar inntrykket av at tenesta ikkje svarer.

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

    Hvis du har ei Producer/Consumer-arkitektur (t.d. jobbar blir lagt i ei kø), treng du eit signal som vekkjer consumerane. Oft er det eit ekstra TEvent (Work available). Consumeren ventar då på to handles: „Work“ eller „Stop“. Ved stopp sette du Stop-Eventet og eventuelt også Work-Eventet, slik at alle consumerar garantert kjem ut av ventinga.

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

    Ved databaseaksessar eller HTTP-kall avgjerst det om tenesta di stoppar reint. I drift gjeld: Kein Call ohne Timeout. Ein Timeout er ingen luksus, men føresetnad for styrbarheit. I tillegg bør du mellom retries/backoff-fasar alltid sjekke Stop. Elles får du klassikaren: „Tenesta stoppar ikkje fordi han held på med 10 Retries og Sleep“.

    I nokre bibliotek kan du trigge avbrot eksplisitt (t.d. Query-Abbruch). Hvis det ikkje går, må du i det minste konfigurere timeoutane korte nok til at dei ikkje sprenger shutdown-timeouten.

    Stop Pending korrekt: Status, Logging und Erwartungsmanagement

    Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
    Fase-logs og tidmåling gjer stop-timeouts reproducerbare og forklarlege.

    Når ei teneste stoppar, er det frå driftssynspunkt viktig å forstå kor ho heng. Til det treng du to ting:

    • Log-Marker i stoppprosessen: „Stop forespurt“, „ingen nye Jobs“, „venter på Worker“, „Worker X avslutta“, „Shutdown ferdig“.
    • Målbare tider: Kor lenge tek stoppet? Kva fase et tid? Her held ofte eit monotont tidsmål som GetTickCount64 eller TStopwatch (monotont = ikkje påverka av systemklokkeendringar).

    Hvis du i stoppstien berre skriv ein einaste loggpost «Stopping…», blir feilsøking i feltet eit gjetteleik. I tenestedrift er loggar ofte det einaste du får utan interaksjon.

    Kva for loggar er verkeleg nyttige i tenester?

    • Service-PID, starttid, versjon/Build (utan overdrevent overhead).
    • Tal aktive Worker, tal på in-flight Jobs.
    • Aktive eksterne avhengigheiter: «DB-Call køyrer», «HTTP-Request køyrer», «Datei-Flush køyrer» (berre aggregert, ikkje kvart detalj).
    • Stopp-timeout nådd: kva Worker er framleis opne?

    Feilsøking i felt: gjera det reproduserbart i staden for å gjette

    Stoppproblem oppstår ofte berre i produksjon: annan last, andre latensar, andre rettar, andre patch-tidsvindauge. Nokre praksisprøvde tiltak:

    Test tenesta under kontroll

    • Stopp under aktiv prosessering (ikkje i idle).
    • Stopp under ekstern feil: DB kort tid utilgjengeleg, HTTP-endepunkt tregt, Fileshare borte.
    • Stopp rett etter oppstart (Race-Conditions: Worker framleis i oppbygging).

    Event Viewer og Service Control Manager-signalar

    Windows skriv tenestehendingar, men dei er ofte grove. Endå betre er det om tenesta di sjølv skriv til ei loggfil eller Windows Event Log. Viktig: logging må framleis fungere i stoppstien. Dersom du slepp loggaren for tidleg under shutdown eller om Flush blokkerer, mistar du nett dei avgjerande spora.

    Gjer hengande trådar synlege

    Når du gjentekne gonger ser «Stop Timeout», lønner det seg å sjå på trådtilstandar (t.d. per Debugger/Procdump i testmiljø). Oft finn du då ein tråd i ein ventetilstand på eit Handle som aldri blir signalisert, eller i ein nettverkskall utan timeout. Fiksen er sjeldan «enda meir Sleep», men ein rein avbrotveg.

    Når lønner innsatsen seg verkeleg?

    Ein minimalistisk teneste som berre har ein Timer og inga eksterne avhengigheiter kan av og til «einfach stoppen». Men så snart éin av følgjande tilfelle gjeld, lønner det seg nesten alltid med ein ryddig Graceful Shutdown:

    • Tjenesta handsamar Jobs mit Seiteneffekten (skrive filer, DB-transaksjonar, API-kall).
    • Det finst mehrere Threads eller ein Pool.
    • Tjenesta er knytt til Netzwerkressourcen (DB, REST, Message Broker, Fileshares).
    • Drift krev planbare Wartungsfenster (Reboots, Updates, Failover).

    Gevinsten er ikkje «Eleganz», men driftsikkerheit: færre harde prosessavbrot, færre inkonsistente mellomtilstandar, færre manuelle inngrep.

    Praksisfallgruver: kva som ofte går gale ved shutdown

    1) Stopp blir sett, men nye Jobs kjem framleis inn

    Dersom du tek imot innkomande arbeid (t.d. per Socket, Datei-Trigger, Timer), må du i stoppstien først stoppe Annahme neuer Arbeit: Listener stengjast, Timer deaktivert, Scheduler stogga. Elles spring du etter avslutninga fordi nye Jobs framleis startar.

    2) Cleanup blokkerer (Flush, Close, Finalize)

    «Berre ein rask flush» kan vere farleg i tenestekontekst viss målet (nettverksdisk, Remote-Log, DB) heng. Difor: Cleanup ja, men med begrensa tid. Ved behov må du avgjere kva data du kan misse i minnet i staden for å blokkere heile stoppen.

    3) Lås og rekkefølgje

    Ved stopp aksesserer du ofte dei same datastrukturane som Worker (Queues, Caches, States). Dersom stopp-tråden held låsar og deretter ventar på at Worker skal avslutte, medan Worker treng same lås, får du ein stopp-deadlock. Motmiddel: hald låsetida kort, ikkje «vente under lås» i stopp-rutinen, definer klar rekkefølgje.

    4) Samtidigheit ved dobbelt stopp

    I praksis kan stopp bli trigga fleire gonger (t.d. stopp + shutdown, eller stopp kjem igjen). Stopp-rutinen din bør idempotent vere: å setje eit stopp-event er greitt, men dobbel Join/Free-logikk må vernast forsvarleg (t.d. med eit atomisk flagg).

    Operativt perspektiv: kva adminar og IT-leiarar forventar av tenesta

    For drift og administrasjon tel ikkje kor «pen» koden er, men om tenesta:

    • ved stopp avsluttar påliteleg (planleggbar, utan heng),
    • ved stopp ikkje skapar inkonsistente data (t.d. halve filer, opne transaksjonar),
    • ved feil leverer brukbare loggar,
    • under vedlikehaldsvindauge og deploys er forutsigbar.

    Det er også grunnen til at temaet stopp-timeout ikkje berre er «utviklarting»: det påverkar patch-syklusar, gjenopprettingstider og om automatiserte deploys i det heile er mogleg.

    Konkrete retningslinjer for eit robust shutdown-design

    Om du vil standardisere temaet pragmatisk, har desse retningslinjene vist seg nyttige:

    • Eit globalt stopp-event, Manual Reset, oppretta tidleg i livssyklusen til tenesta, frigitt seint.
    • Ikkje Sleep i Worker-loopar utan stopp-venleg alternativ (Wait med timeout).
    • Alle eksterne kall med timeouts (DB, HTTP, fildelingar). Vel timeouts slik at dei passar innanfor ditt stopp-timeout.
    • Stopp-timeout som konfigurasjon (t.d. i INI/Registry), slik at drift kan reagere utan å kompilere på nytt.
    • Trinnmodell: fyrst graceful (pågåande jobbar får fullføre), deretter valfritt «soft abort» (ingen nye steg), til slutt hard exit som siste utveg.
    • Gode stopp-loggar med fasar og tidsmåling.

    Konklusjon: TEvent + stopp-timeout er ikkje luksus, men styrbarheit

    Ein hengjande stopp er sjeldan ein enkeltfeil – som regel er det eit arkitekturhol: arbeid køyrer i trådar eller i blokkerande kall som ikkje kjenner eit felles stopp-signal. Med eit tydeleg stopp-event (TEvent, Manual Reset), stopp-venlege Waits i staden for Sleep, konsekvente timeouts for eksterne avhengigheiter og eit definert stopp-timeout får du ei teneste som er forutsigbar i kvardagen.

    Koden lønar seg særleg om tenesta di køyrer i produksjonsmiljø med vedlikehaldsvindauge, automatiserte deploys eller kritiske sideeffekt. Då er «Graceful Shutdown» ikkje kosmetikk, men ein byggestein for stabil drift og færre eskalasjonar ved neste omstart.

    Om du vil setje opp stopp-rutinen deira skikkeleg, eller gjennomgå ein eksisterande Delphi-teneste for robust shutdown-logikk og driftssikkerheit, er ei teknisk sparringssamtale ofte den raskaste vegen til konkrete tiltak: ta kontakt.

    For dette temaet er også Delphi Windows Service og Tevent Delphi viktige. Innlegget plasserer desse aspekta på ein forståeleg måte og syner kva som tel i kvardagen.

    Drøfte prosjekt eller moderniseringsprosjekt med Net-Base.

    neste steg

    Når temaet blir eit reelt prosjekt, bør arkitektur, eksisterande system og drift tidleg saman vurderast.

    Vi støttar ikkje berre ved enkeltspørsmål, men òg når korte kildekodesnuttar, legacy-tema eller portalidéar skal utviklast til eit robust bedriftsprosjekt.

    • Eksisterande tilstand, målbiletet og tekniske risikoar blir vurderast samla.
    • REST, datatilgang, portalar og utrulling blir ikkje utsett til seinare fasar.
    • De ser tidleg kva veg som er økonomisk og driftsmessig berekraftig.

    Del innlegg

    Del dette innlegget direkte

    LinkedIn, X, XING, Facebook, WhatsApp og e-post er straks tilgjengelege. For Instagram klargjer vi lenke og kort tekst med det same.

    E-post

    Instagram opnar i ein ny fane. Lenkje og kort tekst blir kopiert til utklippstavla på førehand.