Net-Base Lehti

02.08.2026

Windows-palvelu Delphi: Hallittu sammutus TEventin ja Stop-timeoutin oikea toteutus

Kun Windows-palvelu jumittuu pysäytettäessä, se ei yleensä ole sattumaa: useimmiten säikeet, I/O tai sleep-silmukat estävät etenemisen ilman keskeytyspolkua. Tämä käytännön artikkeli näyttää, miten Delphi:ssä TEventillä toteutat hallitun sulkemisen (graceful shutdown) ja käsittelet pysäytyksen aikakatkaisut oikein...

02.08.2026

Lehden aiheesta projektikäytäntöön

Artikkeliin liittyvät palvelu- ja tekniikkasivut

Yksi Windows Service in Delphi vaikuttaa arjessa usein vaatimattomalta: pyörii taustalla, käsittelee töitä, kirjoittaa lokit, kommunikoidaan tietokantojen tai REST-API:en kanssa. Kunnes joku klikkaa «Pysäytä palvelu» — tai on patch-reboot — eikä palvelu pysähdy puhtaasti. Silloin palveluiden konsoli näyttää minuutteja „Wird beendet…“, palvelu jumittuu Stop Pending-tilaan, ja pahimmillaan prosessi tapetaan väkisin. Juuri tässä kannattaa käsitellä Windows Service in Delphi hallittu sammutus tietoisena arkkitehtuurikysymyksenä: selkeä sammutussignaali, määritellyt aikakatkaisut ja säikeet, jotka todella reagoivat.

Tässä kirjoituksessa ei mennä frameworkin sisäisiin yksityiskohtiin, vaan esitetään käytännöllinen malli: TEvent stop-signaalina (ydintason synkronointikohde luokasta System.SyncObjs), yhdistettynä Stop-Timeout-strategiaan, joka ottaa huomioon sekä Windows Service Control Managerin (SCM, eli Windows-komponentin, joka käynnistää/pysäyttää palveluita) että omat worker-säikeesi. Mukana tyypilliset reunatapaukset, debuggauslähestymistavat ja kysymys, milloin lisälogiikka oikeasti kannattaa.

Windows Service in Delphi hallittu sammutus käytännössä

Yleisin syy on yksinkertainen: palvelulla on vähintään yksi säie, joka on jumissa blokkaavassa operaatiossa eikä tunne peruutusreittiä. Klassikkoja:

  • Polling-silmukat, joissa käytetään Sleep: „while not Terminated do Sleep(1000)“. Pysäytyskomento saapuu, mutta säie reagoi vasta jopa sekunnin (tai 30 sekunnin…) viiveellä.
  • Blokkaavat I/O-operaatiot: tietokantakutsut, HTTP-pyynnöt, nimettyjen putkien käyttö, tiedostojärjestelmän odotukset — kaikki, mikä „vain odottaa“, ilman että se kuuntelee pysäytyssignaalia.
  • Jonon kuluttaja ilman herätystä: worker odottaa jonosta, mutta pysäytyksessä sitä ei herätetä poistumaan.
  • Lukkojen järjestys / deadlockit: pysäytyspolulla suoritetaan „siivousta“ samalla kun muut säikeet pitävät vielä lukkoja. Tämä ilmenee usein vain pysäytyspolussa, koska lukkojen järjestys poikkeaa normaalisuorituksesta.

Windows SCM odottaa, että palvelu reagoi Stop-komentoon nopeasti ja raportoi tilansa jatkuvasti (kautta SetServiceStatus; Delphi kapseloi tämän service-komponentissa). Jos vastaanotat Stop-tapahtuman mutta et alasajoa suorittaessasi pysäytä säikeitäsi kunnolla, prosessi pysyy elossa — ja Windows päättää jossain vaiheessa, että se „kestää liian kauan“. Lopputulos on joko karkea katkaisu tai palvelu, joka jumiutuu epäselvään välitilaan.

Perusperiaate: Lopetussignaali, jonka jokainen työntekijäsäie ymmärtää

Abstrakte Grafik: Worker-Threads warten auf Work oder Stop-Event und beenden geordnet
Kun työntekijät odottavat „työtä tai pysäytystä“, pysäytysviive pienenee ilman pollausta.

Hallittu sammutus toimii vain, jos sinulla on signaali, joka:

  • voidaan tarkkailla kaikkien asiaankuuluvien säikeiden toimesta,
  • vaikuttaa myös estävistä odotustiloista käsin,
  • pysäytyspolulla deterministinen on (ei „ehkä se joskus pääsee ulos“ -toivo),
  • selkeä Timeout-Strategie on.

Delphi:ssa TEvent on tähän erittäin käyttökelpoinen työkalu: tapahtumaobjekti, joka toteutetaan sisäisesti Windows-handleilla (vertaa CreateEvent/SetEvent). Voit käyttää sitä ‚Stop requested‘ -signaalina. Jokainen Worker ei odota sokeasti, vaan odottaa „työtä oder pysäytystä“.

TEvent richtig wählen: ManualReset vs. AutoReset

Pysäytyssignaaleissa yleensä haluat Manual Reset (manuaalisesti palautettava): kerran asetettuna Event pysyy „signaled“, kunnes palautat sen. Näin varmistetaan, että jokainen säie, joka myöhemmin menee odotustilaan, tunnistaa Stop-signaalin. Auto Reset olisi tässä riskialtis, koska se palauttaa signaalin automaattisesti yhden odottavan säikeen jälkeen ja muut säikeet saattavat jäädä ilman Stop-signaalia.

Delphi-palvelun elinkaari: mihin Stop todella vaikuttaa

Delphi-Windows- und Linux-Services perustuvat tyypillisesti TService:iin (VCL/RTL). SCM lähettää komentoja (Start, Stop, Pause, Continue). Delphi kutsuu sitten vastaavia eventtejä/metodeja (mallista riippuen esim. OnStart, OnStop, OnExecute).

Tärkeää arkkitehtuurille:

  • OnStop ei ole paikka pitkään odotteluun ilman tilapäivityksiä. Se on paikka, jossa käynnistät sammutuksen ja odotat sitten kontrolloidusti – aikakatkaisulla.
  • OnExecute on usein silmukka. Jos siellä työskentelet „endlos“, on silmukan reagoitava Stop-signaaliin.
  • Worker-Threads (TThread tai Thread-Pools) täytyy reagoida samaan Stop-signaaliin, muuten palvelu on loogisesti pysäytetty mutta fyysisesti vielä kesken.

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

Käytännönmukainen malli koostuu neljästä vaiheesta:

  1. Stop anfordern: aseta Stop-Event, älä ota enää uusia töitä vastaan.
  2. Wakeups auslösen: jos Workerit odottavat queueilla tai Sleep-tilassa, niiden pitää voida „herätä“ (esim. Event/Queue-signaalilla).
  3. Geordnet beenden: Workerit lopettavat silmukkansa, sulkevat resurssit (DB-yhteydet, tiedostot, handlet) ja ilmoittavat „fertig“.
  4. Timeout und Fallback: jos kaikki ei pääty ajoissa, sinun täytyy tehdä päätös: odottaa lisää (tilapäivityksellä) tai keskeyttää kontrolloidusti / lopettaa järeästi (riskistä riippuen).

Kärki on: yksikään säie ei saa odottaa pelkästään ajan kulumista (Sleep) tai olla pelkästään I/O-blokissa ilman rinnakkaista Stop-signaalin huomioimista. Sen sijaan käytä odotustoimintoja, jotka huomioivat useita signaaleja (esim. „Stop-Event oder Work-Event“), tai kapsuloi I/O aikakatkaisuilla ja Stop-tarkistuksilla.

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

Tässä syntyy projekteissa eniten väärinkäsityksiä. On olemassa kaksi erillistä aikakatkaisutasoa:

  • SCM-odotus: Windows odottaa, että tilassa SERVICE_STOP_PENDING raportoit edistymisestä säännöllisesti. Muuten näyttää siltä, että olet jumissa. Delphi hoitaa osittain tätä, mutta kun itse olet pidempään estynyt, tarvitset strategian sen varmistamiseksi, miten mahdollistat jatkossa tilapäivitykset (tai miten pidät pysäytysvaiheen lyhyenä).
  • Oma sammutusaikakatkaisu: Määrittelet esimerkiksi „Annamme itsellemme 20 sekuntia käynnissä olevien töiden siistiin päättämiseen, sitten keskeytämme.“ Tämä on arkkitehtuuripäätös: tietojen konsistenssi vs. pakotettu uudelleenkäynnistys vs. käyttöön liittyvät vaatimukset.
  • Käytännössä tämä tarkoittaa: palvelusi tulisi nopeasti siirtyä tilaan, jossa se ei enää käynnistä uusia työyksiköitä, ja sen jälkeen ainoastaan odottaa käynnissä olevien töiden päättymistä – mutta ei ikuisesti. Tämä odotusvaihe tulisi toteuttaa pienissä jaksoissa, jotta voit reagoida ja tarvittaessa kirjata lokit.

    Kuinka pitkään pysäytys voi kestää?

    Ei ole yhtä maagista lukua, joka sopisi aina. Monille liiketoimintapalveluille tavoitealue 5–30 sekuntia on realistinen: riittävästi aikaa „in-flight“-datoille, mutta lyhyt riittävän patch-ikkunoille. Jos tarvitset säännöllisesti pidempään, se viittaa usein siihen, että käsittelet liian suuria eräkokonaisuuksia kerralla tai että ulkoiset riippuvuudet (DB/HTTP) toimivat ilman aikakatkaisua.

    Implementointi TEventillä: rakenne, joka pysyy käytössä vakaana

    Hyväksi koettu rakenne Delphi-palvelussa näyttää tältä (ilman kehyskohtaisia yksityiskohtia):

    • Yksi Stop-Event (TEvent, Manual Reset), joka asetetaan pysäytyksen yhteydessä.
    • Yksi tai useampi Worker-Thread, jotka pääsilmukassaan tarkistavat säännöllisesti Stop-tilan.
    • Valinnaisesti yksi Work-Event tai jono, joka signaloi töitä. Workerit odottavat sitten „Work oder Stop“.
    • Yksi Shutdown-Phase, joka workerit „joinaa“ (eli odottaa, kunnes ne ovat päättyneet), mutta aikakatkaisulla.

    Päätös ei ole siinä, käytätkö TThreadia, omnithreadlibrarya vai omaa poolia, vaan että workerisi eivät juokse „sokeina“. Worker-loopin tulisi rakenteellisesti näyttää tältä: odotus tapahtumalle(t) → työ pienissä paloissa → palojen välillä Stop-tilan tarkistus → resurssien siisti vapauttaminen.

    Karikko: pelkkä Terminate ei riitä

    Monet Delphi-säikeet keskeytetään käyttämällä Terminate-lipun asettelua. Se on kuitenkin vain lippu. Jos säie on parhaillaan estävässä API-kutsussa, ei tapahdu mitään. Siksi oma Stop-event on hyödyllinen: voit integroida sen odotuskutsuihin ja aiheuttaa tarkoituksenmukaisia herätyksiä.

    Karikko: FreeOnTerminate palvelukontekstissa

    Palveluissa nähdään usein FreeOnTerminate := True. Se voi toimia, mutta tekee sammutuksesta vaikeammin hallittavaa, koska sinulla ei usein enää ole siistiä viitettä odottaa säikeen päättymistä tai kirjata virhetiloja. Kontrolloidun pysäytyslogiikan kannalta on yleensä vakaampaa omistaa säikeet eksplisiittisesti ja odottaa sekä vapauttaa ne deterministisesti sammutuksessa.

    Estävät operaatiot: näin teet niistä pysäytettäviä

    Troubleshooting-Szene: Netzwerkverbindung als Ursache für blockierende Calls und Timeouts
    Estäytyvä I/O ilman aikakatkaisua on yleisin syy palvelun pysähtymiseen.

    Vaikein osa ei ole itse tapahtuma, vaan ne kohdat, joissa palvelusi lukkiutuu. Kolme tyypillistä luokkaa:

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

    Jos suoritat tehtäviä periodisesti (esim. „tarkista 10 sekunnin välein“), älä käytä Sleep(10000), vaan odota tapahtumaa aikakatkaisulla. Näin Stop-Event voi lopettaa odotuksen välittömästi. Tämä pienentää pysäytysviivettä ja estää vaikutelman „palvelu ei reagoi“.

    2) Queue-Consumer: Work-Eventin ja Stop-Eventin yhdistäminen

    Jos sinulla on producer/consumer-arkkitehtuuri (esim. työt laitetaan jonoon), tarvitset signaalin, joka herättää consumerit. Usein se on toinen TEvent (Work available). Consumer odottaa sitten kahta handlea: „Work“ tai „Stop“. Stop-tilanteessa asetat Stop-Eventin ja tarvittaessa myös Work-Eventin, jotta kaikki consumerit varmasti poistuvat odotuksesta.

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

    Tietokantakyselyissä tai HTTP-kutsuissa ratkaistaan, pysähtyykö palvelusi siististi. Käytännössä: Ei yhtään kutsua ilman aikakatkaisua. Aikakatkaisu ei ole ylellisyys vaan edellytys hallittavuudelle. Lisäksi sinun tulisi tarkistaa Stop aina retry-/backoff-vaiheiden välillä. Muuten saat klassikon: „palvelu ei pysähdy, koska se tekee juuri 10 retryä Sleepin kanssa“.

    Joissain kirjastoissa voit laukaista keskeytyksiä eksplisiittisesti (esim. kyselyn peruutus). Jos se ei ole mahdollista, sinun täytyy ainakin konfiguroida aikakatkaisut tarpeeksi lyhyiksi, jotta ne eivät ylitä sammutuksen aikakatkaisua.

    Stop Pending oikein: tila, lokitus ja odotusten hallinta

    Zeitmessung und Log-Analyse zur Diagnose von Stop-Timeouts bei Windows-Services
    Vaihekohtaiset lokimerkinnät ja ajanmittaus tekevät Stop-Timeoutit toistettaviksi ja selitettäväksi.

    Kun palvelu pysähtyy, tuotannon näkökulmasta on tärkeää ymmärtää, missä se jumittaa. Tarvitset siihen kaksi asiaa:

    • Lokitunnisteet pysäytyspolulla: „Pysäytys pyydetty“, „ei uusia töitä“, „odotetaan workeria“, „Worker X lopetti“, „sammutus valmis“.
    • Mitattavat ajat: Kuinka kauan pysäytys kestää? Mikä vaihe vie aikaa? Usein riittää monotoninen ajanmittaus kuten GetTickCount64 tai TStopwatch (monotoninen = ei vääristy järjestelmäajan muutosten vuoksi).

    Wenn du im Stop-Pfad nur einen einzigen Logeintrag „Stopping…“ schreibst, bleibt das Debugging im Feld ein Ratespiel. Im Service-Betrieb sind Logs häufig das einzige, was du ohne Interaktion bekommst.

    Welche Logs sind in Services wirklich hilfreich?

    • Service-PID, Startzeit, Version/Build (ohne übertriebenen Overhead).
    • Anzahl aktiver Worker, Anzahl in-flight Jobs.
    • Aktive externe Abhängigkeiten: „DB-Call läuft“, „HTTP-Request läuft“, „Datei-Flush läuft“ (nur aggregiert, nicht jedes Detail).
    • Stop-Timeout erreicht: welche Worker sind noch offen?

    Debugging im Feld: reproduzierbar machen statt raten

    Die Stop-Probleme treten gern nur in Produktion auf: andere Last, andere Latenzen, andere Rechte, andere Patch-Zeitfenster. Ein paar praxiserprobte Hebel:

    Service unter Kontrolle testen

    • Stop während aktiver Verarbeitung (nicht im Idle).
    • Stop während externer Störung: DB kurz nicht erreichbar, HTTP-Endpunkt langsam, Fileshare weg.
    • Stop unmittelbar nach Start (Race-Conditions: Worker noch im Aufbau).

    Event Viewer und Service Control Manager Signale

    Windows schreibt Dienstereignisse, aber die sind oft grob. Besser ist, wenn dein Service selbst in eine Logdatei oder Windows Event Log schreibt. Wichtig ist dabei: Logging sollte im Stop-Pfad noch funktionieren. Wenn du Logger im Shutdown zu früh freigibst oder Flush blockiert, verlierst du genau die entscheidenden Spuren.

    Hängende Threads sichtbar machen

    Wenn du wiederholt „Stop Timeout“ siehst, lohnt sich ein Blick auf Thread-Zustände (z. B. per Debugger/Procdump in Testumgebung). Häufig findest du dann einen Thread in einem Wait-Zustand auf einem Handle, das nie signalisiert wird, oder in einem Netzwerk-Call ohne Timeout. Der Fix ist dann selten „noch mehr Sleep“, sondern ein sauberer Abbruchpfad.

    Wann lohnt sich der Aufwand wirklich?

    Ein minimalistischer Dienst, der nur einen Timer hat und keine externen Abhängigkeiten, kann manchmal „einfach stoppen“. Sobald aber eines davon zutrifft, lohnt sich ein sauberer Graceful Shutdown fast immer:

    • Der Service verarbeitet Jobs mit Seiteneffekten (Dateien schreiben, DB-Transaktionen, API-Aufrufe).
    • Es gibt mehrere Threads oder einen Pool.
    • Der Service hängt an Netzwerkressourcen (DB, REST, Message Broker, Fileshares).
    • Der Betrieb fordert planbare Wartungsfenster (Reboots, Updates, Failover).

    Der Mehrwert ist nicht „Eleganz“, sondern Betriebssicherheit: weniger harte Prozessabbrüche, weniger inkonsistente Zwischenzustände, weniger manuelle Eingriffe.

    Praxis-Fallstricke: Was im Shutdown gern schiefgeht

    1) Stop wird gesetzt, aber neue Jobs kommen trotzdem rein

    Wenn du eingehende Arbeit annimmst (z. B. per Socket, Datei-Trigger, Timer), musst du im Stop-Pfad zuerst die Annahme neuer Arbeit stoppen: Listener schließen, Timer deaktivieren, Scheduler anhalten. Sonst rennst du dem Ende hinterher, weil immer noch neue Jobs starten.

    2) Cleanup blockiert (Flush, Close, Finalize)

    „Nur noch schnell alles flushen“ kann im Service-Kontext gefährlich sein, wenn das Ziel (Netzlaufwerk, Remote-Log, DB) gerade hängt. Deshalb: Cleanup ja, aber mit begrenzter Zeit. Notfalls musst du entscheiden, welche Daten du in-memory verlierst, statt den kompletten Stop zu blockieren.

    3) Locks und Reihenfolge

    Stopin aikana käytät usein samoja tietorakenteita kuin Worker (Queues, Caches, States). Jos Stop-Thread pitää Locks ja sitten odottaa Workerin päättymistä, samalla kun Worker tarvitsee samaa Lockia, syntyy Stop-Deadlock. Vastakeinot: pidä Lockin pitämisaika lyhyenä, älä Stop-polulla odota „Lockin alla“, määrittele selkeä järjestys.

    4) Samanaikaisuus, kun Stop kutsutaan kahdesti

    Käytännössä Stop voidaan laukaista useasti (esim. Stop + Shutdown tai Stop tulee uudelleen). Stop-polun tulee olla idempotentti: Stop-Eventin asettaminen on ok, mutta kaksoinen Join/Free-Logik tulee suojata huolellisesti (esim. atomisella lipulla).

    Operatiivinen näkökulma: mitä järjestelmänvalvojat ja IT-johtajat odottavat palvelulta

    Käytössä ja ylläpidossa lopulta ratkaisevaa ei ole, kuinka „kaunis“ koodi on, vaan täyttääkö palvelu:

    • Stopin yhteydessä päättyy luotettavasti (ennakoitavissa, ilman jumiutumista),
    • Stopin yhteydessä ei synny epäjohdonmukaisia tietoja (esim. puolikkaita tiedostoja, avoimia transaktioita),
    • virhetilanteissa tuottaa käyttökelpoiset lokit,
    • huoltoikkunoilla ja deploymenteissa on ennakoitava.

    Tämä on myös syy, miksi Stop-Timeout ei ole pelkkää „kehittäjätavaraa“: se vaikuttaa korjauspäivityssykleihin, palautumisaikoihin ja siihen, ovatko automatisoidut Deployments ylipäätään mahdollisia.

    Konkreettiset ohjenuorat vankkaan Shutdown-suunnitteluun

    Jos haluat standardisoida aihetta pragmaattisesti, nämä ohjenuorat ovat osoittautuneet toimiviksi:

    • Yksi globaali Stop-Event, Manual Reset, luodaan varhain palvelun elinkaaressa, vapautetaan vasta myöhään.
    • Ei Sleep Worker-loopeissa ilman Stop-kykyistä vaihtoehtoa (Wait mit Timeout).
    • Kaikilla ulkoisilla kutsuilla Timeouts (DB, HTTP, Fileshares). Valitse timeoutit siten, että ne sopivat Shutdown-Timeoutiin.
    • Stop-Timeout konfiguroitavana (esim. INI/Registry), jotta käyttö voi reagoida ilman uudelleenkääntämistä.
    • Vaihemalli: ensin graceful (käynnissä olevat tehtävät valmiiksi), sitten valinnainen „soft abort“ (ei uusia vaiheita), lopulta kova Exit viimeisenä keinona.
    • Hyvät Stop-lokit vaiheilla ja ajanseurannalla.

    Yhteenveto: TEvent + Stop-Timeout ei ole ylellisyyttä, vaan ohjattavuutta

    Jumiutunut Stop harvoin on yksittäinen virhe – useimmiten se on arkkitehtuurin aukko: työ suoritetaan threadeissa tai blokkautuvissa kutsuissa, jotka eivät tunne yhteistä Stop-signaalia. Selkeällä Stop-Eventillä (TEvent, Manual Reset), stop-kykyisillä Waits Sleepin sijaan, johdonmukaisilla Timeouts ulkoisille riippuvuuksille ja määritellyllä Shutdown-Timeoutilla saat palvelun, joka on arjessa ennakoitava.

    Koodi kannattaa erityisesti, jos palvelusi toimii tuotantoympäristössä huoltoikkunoiden, automatisoitujen Deploymentsien tai kriittisten sivuvaikutusten kanssa. Silloin „Graceful Shutdown“ ei ole kosmeettista, vaan osa vakaata käyttöä ja vähemmän eskalaatioita seuraavassa uudelleenkäynnistyksessä.

    Jos haluat asettaa Stop-polun kunnolla tai tarkistaa olemassa olevan Delphi-palvelun robustille Shutdown-logiikalle ja toimintavarmuudelle, tekninen sparrings-call on usein nopein tie selkeisiin toimenpiteisiin: Ota yhteyttä.

    Tästä aiheesta ovat myös Delphi Windows Service ja Tevent Delphi tärkeitä. Artikkeli jäsentää nämä näkökohdat ymmärrettävästi ja näyttää, mihin arjessa kannattaa kiinnittää huomiota.

    Keskustele projektista tai modernisointihankkeesta Net-Base kanssa.

    Seuraava vaihe

    Kun aiheesta muodostuu todellinen projekti, arkkitehtuuri, nykytila ja operointi on tarkasteltava yhdessä varhaisessa vaiheessa.

    Emme tue pelkästään yksittäiskysymyksissä, vaan myös silloin, kun lähdekoodipalasista, legacy-aiheista tai portaali-ideoista halutaan muodostaa luotettava yrityshanke.

    • Nykytila, tavoitetila ja tekniset riskit arvioidaan yhdessä.
    • REST, tietojen käyttö, portaalit ja käyttöönotto eivät siirry myöhempään vaiheeseen.
    • Näette ajoissa, mikä vaihtoehto on taloudellisesti ja operatiivisesti kannattava.

    Jaa artikkeli

    Jaa tämä viesti suoraan

    LinkedIn, X, XING, Facebook, WhatsApp ja sähköposti ovat välittömästi saatavilla. Instagramia varten valmistelemme linkin ja lyhyen tekstin.

    Sähköposti

    Instagram avautuu uuteen välilehteen. Linkki ja lyhyt teksti kopioidaan ensin leikepöydälle.