signal — Ställ in hanterare för asynkrona händelser

Källkod: Lib/signal.py


Denna modul tillhandahåller mekanismer för att använda signalhanterare i Python.

Allmänna regler

Funktionen signal.signal() gör det möjligt att definiera anpassade hanterare som ska köras när en signal tas emot. Ett litet antal standardhanterare är installerade: SIGPIPE ignoreras (så att skrivfel på pipes och sockets kan rapporteras som vanliga Python-undantag) och SIGINT översätts till ett KeyboardInterrupt-undantag om den överordnade processen inte har ändrat det.

En hanterare för en viss signal, när den väl är inställd, förblir installerad tills den uttryckligen återställs (Python emulerar BSD-stilgränssnittet oavsett den underliggande implementeringen), med undantag för hanteraren för SIGCHLD, som följer den underliggande implementeringen.

På WebAssembly-plattformar är signaler emulerade och beter sig därför annorlunda. Flera funktioner och signaler är inte tillgängliga på dessa plattformar.

Exekvering av Python-signalhanterare

En Python-signalhanterare exekveras inte inuti lågnivåsignalhanteraren (C). Istället sätter signalhanteraren på låg nivå en flagga som talar om för virtual machine att exekvera motsvarande Python-signalhanterare vid ett senare tillfälle (till exempel vid nästa bytecode-instruktion). Detta har konsekvenser:

  • Det är meningslöst att fånga synkrona fel som SIGFPE eller SIGSEGV som orsakas av en ogiltig operation i C-koden. Python kommer att återvända från signalhanteraren till C-koden, som sannolikt kommer att ge upphov till samma signal igen, vilket gör att Python uppenbarligen hänger sig. Från och med Python 3.3 och framåt kan du använda modulen faulthandler för att rapportera synkrona fel.

  • En långdragen beräkning som implementeras enbart i C (t.ex. matchning av reguljära uttryck i en stor textmassa) kan köras oavbrutet under godtycklig tid, oavsett vilka signaler som tas emot. Pythons signalhanterare kommer att anropas när beräkningen är klar.

  • Om hanteraren skapar ett undantag kommer det att skapas ”ur tomma intet” i huvudtråden. Se noten nedan för en diskussion.

Signaler och trådar

Pythons signalhanterare exekveras alltid i huvudtolkens Python-tråd, även om signalen mottogs i en annan tråd. Detta innebär att signaler inte kan användas som ett medel för kommunikation mellan trådar. Du kan använda synkroniseringsprimitiven från modulen threading istället.

Dessutom är det bara huvudtråden i huvudtolken som får ställa in en ny signalhanterare.

Modulens innehåll

Ändrad i version 3.5: signal (SIG*), hanterare (SIG_DFL, SIG_IGN) och sigmask (SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK) relaterade konstanter listade nedan omvandlades till enums (Signals, Handlers respektive Sigmasks). funktionerna getsignal(), pthread_sigmask(), sigpending() och sigwait() returnerar mänskligt läsbara enums som Signals-objekt.

Signalmodulen definierar tre enumer:

class signal.Signals

enum.IntEnum samling av SIG*-konstanter och CTRL_*-konstanter.

Tillagd i version 3.5.

class signal.Handlers

enum.IntEnum samlar in konstanterna SIG_DFL och SIG_IGN.

Tillagd i version 3.5.

class signal.Sigmasks

enum.IntEnum samlar konstanterna SIG_BLOCK, SIG_UNBLOCK och SIG_SETMASK.

Tillgänglighet: Unix.

Se man-sidorna sigprocmask(2) och pthread_sigmask(3) för ytterligare information.

Tillagd i version 3.5.

De variabler som definieras i modulen signal är:

signal.SIG_DFL

Detta är ett av två standardalternativ för signalhantering; det kommer helt enkelt att utföra standardfunktionen för signalen. På de flesta system är till exempel standardåtgärden för SIGQUIT att dumpa kärnan och avsluta, medan standardåtgärden för SIGCHLD är att helt enkelt ignorera den.

signal.SIG_IGN

Detta är en annan standardsignalhanterare, som helt enkelt ignorerar den givna signalen.

signal.SIGABRT

Avbrottssignal från abort(3).

signal.SIGALRM

Timersignal från alarm(2).

signal.SIGBREAK

Avbrott från tangentbordet (CTRL + BREAK).

Tillgänglighet: Windows.

signal.SIGBUS

Bussfel (dålig minnesåtkomst).

signal.SIGCHLD

Barnprocessen stoppades eller avslutades.

signal.SIGCLD

Alias till SIGCHLD.

Tillgänglighet: not macOS.

signal.SIGCONT

Fortsätt processen om den för närvarande är stoppad

signal.SIGFPE

Undantag för flyttal. Till exempel division med noll.

Se även

ZeroDivisionError uppstår när det andra argumentet i en divisions- eller modulooperation är noll.

signal.SIGHUP

Upphängning upptäckt på styrande terminal eller död i styrande process.

signal.SIGILL

Olaglig instruktion.

signal.SIGINT

Avbrott från tangentbordet (CTRL + C).

Standardåtgärden är att skapa KeyboardInterrupt.

signal.SIGKILL

Dödssignal.

Den kan inte fångas, blockeras eller ignoreras.

signal.SIGPIPE

Trasigt rör: skriv till ett rör utan läsare.

Standardåtgärden är att ignorera signalen.

signal.SIGSEGV

Segmenteringsfel: ogiltig minnesreferens.

signal.SIGSTKFLT

Stapelfel på coprocessor. Linuxkärnan ger inte ut den här signalen: den kan bara ges ut i användarutrymmet.

Tillgänglighet: Linux.

På arkitekturer där signalen är tillgänglig. Se man-sidan signal(7) för ytterligare information.

Tillagd i version 3.11.

signal.SIGTERM

Signal för avslutning.

signal.SIGUSR1

Användardefinierad signal 1.

signal.SIGUSR2

Användardefinierad signal 2.

signal.SIGWINCH

Signal för ändring av fönsterstorlek.

SIG*

Alla signalnummer är symboliskt definierade. Till exempel definieras hangup-signalen som signal.SIGHUP; variabelnamnen är identiska med de namn som används i C-program och som finns i <signal.h>. Unix man page för ’signal()’ listar de befintliga signalerna (på vissa system är detta signal(2), på andra finns listan i signal(7)). Observera att inte alla system definierar samma uppsättning signalnamn; endast de namn som definieras av systemet definieras av den här modulen.

signal.CTRL_C_EVENT

Den signal som motsvarar händelsen Ctrl+C tangenttryckning. Denna signal kan endast användas med os.kill().

Tillgänglighet: Windows.

Tillagd i version 3.2.

signal.CTRL_BREAK_EVENT

Den signal som motsvarar händelsen Ctrl+Break tangenttryckning. Denna signal kan endast användas med os.kill().

Tillgänglighet: Windows.

Tillagd i version 3.2.

signal.NSIG

Ett mer än numret på det högsta signalnumret. Använd valid_signals() för att få giltiga signalnummer.

signal.ITIMER_REAL

Minskar intervalltimern i realtid och levererar SIGALRM när den löper ut.

signal.ITIMER_VIRTUAL

Minskar intervalltimern endast när processen körs och levererar SIGVTALRM när den löper ut.

signal.ITIMER_PROF

Minskar intervalltimern både när processen körs och när systemet körs på uppdrag av processen. Tillsammans med ITIMER_VIRTUAL används den här timern vanligtvis för att profilera den tid som programmet spenderar i användar- och kärnutrymmet. SIGPROF levereras när den löper ut.

signal.SIG_BLOCK

Ett möjligt värde för parametern how i pthread_sigmask() som anger att signaler ska blockeras.

Tillagd i version 3.3.

signal.SIG_UNBLOCK

Ett möjligt värde för how-parametern till pthread_sigmask() som anger att signaler ska avblockeras.

Tillagd i version 3.3.

signal.SIG_SETMASK

Ett möjligt värde för how-parametern till pthread_sigmask() som anger att signalmasken ska ersättas.

Tillagd i version 3.3.

Modulen signal definierar ett undantag:

exception signal.ItimerError

Utlöses för att signalera ett fel från den underliggande setitimer() eller getitimer() implementationen. Förvänta dig detta fel om en ogiltig intervalltimer eller en negativ tid skickas till setitimer(). Detta fel är en subtyp av OSError.

Tillagd i version 3.3: Detta fel var tidigare en subtyp av IOError, som nu är ett alias av OSError.

Modulen signal definierar följande funktioner:

signal.alarm(time)

Om time inte är noll, begär denna funktion att en SIGALRM-signal skickas till processen om time sekunder. Eventuella tidigare schemalagda larm avbryts (endast ett larm kan schemaläggas åt gången). Det returnerade värdet är då antalet sekunder innan något tidigare inställt larm skulle ha levererats. Om time är noll är inget larm schemalagt och ett eventuellt schemalagt larm avbryts. Om returvärdet är noll är inget larm för närvarande schemalagt.

Tillgänglighet: Unix.

Se manualsidan alarm(2) för ytterligare information.

signal.getsignal(signalnum)

Returnerar den aktuella signalhanteraren för signalen signalnum. Det returnerade värdet kan vara ett anropbart Python-objekt eller ett av specialvärdena signal.SIG_IGN, signal.SIG_DFL eller None. Här betyder signal.SIG_IGN att signalen tidigare ignorerades, signal.SIG_DFL betyder att standardmetoden för att hantera signalen tidigare användes och None betyder att den tidigare signalhanteraren inte installerades från Python.

signal.strsignal(signalnum)

Returnerar beskrivningen av signalen signalnum, t.ex. ”Interrupt” för SIGINT. Returnerar None om signalnum inte har någon beskrivning. Utlöser ValueError om signalnum är ogiltig.

Tillagd i version 3.8.

signal.valid_signals()

Returnerar uppsättningen av giltiga signalnummer på denna plattform. Detta kan vara mindre än range(1, NSIG) om vissa signaler är reserverade av systemet för internt bruk.

Tillagd i version 3.8.

signal.pause()

Får processen att sova tills en signal tas emot; lämplig hanterare kommer då att anropas. Returnerar ingenting.

Tillgänglighet: Unix.

Se man-sidan signal(2) för ytterligare information.

Se även sigwait(), sigwaitinfo(), sigtimedwait() och sigpending().

signal.raise_signal(signum)

Skickar en signal till den anropande processen. Returnerar ingenting.

Tillagd i version 3.8.

signal.pidfd_send_signal(pidfd, sig, siginfo=None, flags=0)

Skicka signalen sig till den process som filbeskrivaren pidfd refererar till. Python har för närvarande inte stöd för parametern siginfo; den måste vara None. Argumentet flags tillhandahålls för framtida tillägg; inga flaggvärden är för närvarande definierade.

Se pidfd_send_signal(2) man page för mer information.

Tillgänglighet: Linux >= 5.1, Android >= build-time API level 31

Tillagd i version 3.9.

signal.pthread_kill(thread_id, signalnum)

Skicka signalen signalnum till tråden thread_id, en annan tråd i samma process som anroparen. Måltråden kan exekvera vilken kod som helst (Python eller inte). Men om måltråden exekverar Python-tolken kommer Python-signalhanterarna att exekveras av huvudtråden i huvudtolken. Därför skulle den enda poängen med att skicka en signal till en viss Python-tråd vara att tvinga ett pågående systemanrop att misslyckas med InterruptedError.

Använd threading.get_ident() eller attributet ident i threading.Thread-objekt för att få ett lämpligt värde för thread_id.

Om signalnum är 0 skickas ingen signal, men felkontroll utförs ändå; detta kan användas för att kontrollera om måltråden fortfarande körs.

Utlöser en auditing event signal.pthread_kill med argumenten thread_id, signalnum.

Tillgänglighet: Unix.

Se man-sidan pthread_kill(3) för ytterligare information.

Se även os.kill().

Tillagd i version 3.3.

signal.pthread_sigmask(how, mask)

Hämta och/eller ändra signalmasken för den anropande tråden. Signalmasken är den uppsättning signaler vars leverans för närvarande är blockerad för den anropande tråden. Returnera den gamla signalmasken som en uppsättning signaler.

Anropets beteende är beroende av värdet på how, enligt följande.

  • SIG_BLOCK: Uppsättningen av blockerade signaler är en förening av den aktuella uppsättningen och argumentet mask.

  • SIG_UNBLOCK: Signalerna i mask tas bort från den aktuella uppsättningen av blockerade signaler. Det är tillåtet att försöka avblockera en signal som inte är blockerad.

  • SIG_SETMASK: Uppsättningen av blockerade signaler ställs in till argumentet mask.

mask är en uppsättning signalnummer (t.ex. {signal.SIGINT, signal.SIGTERM}). Använd valid_signals() för en fullständig mask som inkluderar alla signaler.

Till exempel läser signal.pthread_sigmask(signal.SIG_BLOCK, []) signalmasken för den anropande tråden.

SIGKILL och SIGSTOP kan inte blockeras.

Tillgänglighet: Unix.

Se man-sidorna sigprocmask(2) och pthread_sigmask(3) för ytterligare information.

Se även pause(), sigpending() och sigwait().

Tillagd i version 3.3.

signal.setitimer(which, seconds, interval=0.0)

Ställer in en given intervalltimer (en av signal.ITIMER_REAL, signal.ITIMER_VIRTUAL eller signal.ITIMER_PROF) specificerad av which att starta efter seconds (float accepteras, skiljer sig från alarm()) och därefter var intervall sekund (om intervall inte är noll). Intervalltimern som anges av which kan rensas genom att seconds sätts till noll.

När en intervalltimer startar skickas en signal till processen. Signalen som skickas beror på vilken timer som används; signal.ITIMER_REAL kommer att leverera SIGALRM, signal.ITIMER_VIRTUAL skickar SIGVTALRM, och signal.ITIMER_PROF kommer att leverera SIGPROF.

De gamla värdena returneras som en tupel: (delay, interval).

Försök att skicka en ogiltig intervalltimer kommer att orsaka ett ItimerError.

signal.getitimer(which)

Returnerar aktuellt värde för en given intervalltimer som specificeras av which.

signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)

Ställ in wakeup-filens deskriptor till fd. När en signal som ditt program har registrerat en signalhanterare för tas emot, skrivs signalnumret som en enda byte till fd. Om du inte har registrerat en signalhanterare för de signaler du bryr dig om, skrivs ingenting till wakeup fd. Detta kan användas av ett bibliotek för att väcka ett poll- eller select-anrop, så att signalen kan bearbetas fullständigt.

Den gamla wakeup fd returneras (eller -1 om wakeup för filbeskrivare inte var aktiverad). Om fd är -1 inaktiveras väckning av filbeskrivare. Om fd inte är -1 måste den vara icke-blockerande. Det är upp till biblioteket att ta bort eventuella byte från fd innan poll eller select anropas igen.

När trådar är aktiverade kan den här funktionen endast anropas från huvudtråden i huvudtolken; om du försöker anropa den från andra trådar kommer ett ValueError-undantag att uppstå.

Det finns två vanliga sätt att använda den här funktionen. I båda fallen använder man fd för att vakna när en signal kommer, men sedan skiljer de sig åt i hur de avgör vilken signal eller vilka signaler som har kommit.

I den första metoden läser vi ut data ur fd:ns buffert, och bytevärdena ger dig signalnumren. Det här är enkelt, men i sällsynta fall kan det uppstå problem: i allmänhet har fd en begränsad mängd buffertutrymme, och om för många signaler kommer för snabbt kan bufferten bli full och vissa signaler kan gå förlorade. Om du använder det här tillvägagångssättet bör du ställa in warn_on_full_buffer=True, vilket åtminstone kommer att leda till att en varning skrivs ut till stderr när signaler går förlorade.

I det andra tillvägagångssättet använder vi wakeup fd endast för väckningar och ignorerar de faktiska bytevärdena. I det här fallet är allt vi bryr oss om huruvida fd:s buffert är tom eller inte; en full buffert indikerar inte alls något problem. Om du använder det här tillvägagångssättet bör du ställa in warn_on_full_buffer=False, så att dina användare inte förvirras av falska varningsmeddelanden.

Ändrad i version 3.5: I Windows stöder funktionen nu även socket-handtag.

Ändrad i version 3.7: Lagt till parametern warn_on_full_buffer.

signal.siginterrupt(signalnum, flag)

Ändra beteende för omstart av systemanrop: om flag är False, kommer systemanrop att startas om när de avbryts av signal signalnum, annars kommer systemanrop att avbrytas. Returnerar ingenting.

Tillgänglighet: Unix.

Se manualsidan siginterrupt(3) för ytterligare information.

Observera att installation av en signalhanterare med signal() kommer att återställa omstartbeteendet till avbrytbart genom att implicit anropa siginterrupt() med ett sant flagg-värde för den givna signalen.

signal.signal(signalnum, handler)

Ställ in hanteraren för signalen signalnum till funktionen handler. handler kan vara ett anropbart Python-objekt som tar två argument (se nedan), eller ett av specialvärdena signal.SIG_IGN eller signal.SIG_DFL. Den föregående signalhanteraren kommer att returneras (se beskrivningen av getsignal() ovan). (Se Unix man page signal(2) för ytterligare information)

När trådar är aktiverade kan den här funktionen endast anropas från huvudtråden i huvudtolken; om du försöker anropa den från andra trådar kommer ett ValueError-undantag att uppstå.

Handler anropas med två argument: signalnumret och den aktuella stackramen (None eller ett ramobjekt; för en beskrivning av ramobjekt, se description i typhierarkin eller se attributbeskrivningarna i modulen inspect).

I Windows kan signal() endast anropas med SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM eller SIGBREAK. Ett ValueError kommer att uppstå i alla andra fall. Observera att inte alla system definierar samma uppsättning signalnamn; ett AttributeError kommer att uppstå om ett signalnamn inte är definierat som SIG* konstant på modulnivå.

signal.sigpending()

Undersök uppsättningen signaler som väntar på att levereras till den anropande tråden (dvs. de signaler som har skapats medan de blockerats). Returnera uppsättningen av de väntande signalerna.

Tillgänglighet: Unix.

Se manualsidan sigpending(2) för ytterligare information.

Se även pause(), pthread_sigmask() och sigwait().

Tillagd i version 3.3.

signal.sigwait(sigset)

Avbryter exekveringen av den anropande tråden tills en av de signaler som anges i signaluppsättningen sigset har levererats. Funktionen accepterar signalen (tar bort den från listan över väntande signaler) och returnerar signalnumret.

Tillgänglighet: Unix.

Se manualsidan sigwait(3) för ytterligare information.

Se även pause(), pthread_sigmask(), sigpending(), sigwaitinfo() och sigtimedwait().

Tillagd i version 3.3.

signal.sigwaitinfo(sigset)

Avbryt exekveringen av den anropande tråden tills en av de signaler som anges i signaluppsättningen sigset har levererats. Funktionen accepterar signalen och tar bort den från listan över väntande signaler. Om en av signalerna i sigset redan är väntande för den anropande tråden, kommer funktionen att returnera omedelbart med information om den signalen. Signalhanteraren anropas inte för den levererade signalen. Funktionen ger upphov till ett InterruptedError om den avbryts av en signal som inte finns i sigset.

Returvärdet är ett objekt som representerar de data som finns i strukturen siginfo_t, nämligen: si_signo, si_code, si_errno, si_pid, si_uid, si_status, si_band.

Tillgänglighet: Unix.

Se manualsidan sigwaitinfo(2) för ytterligare information.

Se även pause(), sigwait() och sigtimedwait().

Tillagd i version 3.3.

Ändrad i version 3.5: Funktionen prövas nu igen om den avbryts av en signal som inte finns i sigset och signalhanteraren inte ger upphov till ett undantag (se PEP 475 för motivering).

signal.sigtimedwait(sigset, timeout)

Som sigwaitinfo(), men tar ytterligare ett timeout-argument som anger en timeout. Om timeout anges som 0 utförs en pollning. Returnerar None om en timeout inträffar.

Tillgänglighet: Unix.

Se manualsidan sigtimedwait(2) för ytterligare information.

Se även pause(), sigwait() och sigwaitinfo().

Tillagd i version 3.3.

Ändrad i version 3.5: Funktionen försöker nu igen med den omräknade timeout om den avbryts av en signal som inte finns i sigset och signalhanteraren inte gör ett undantag (se PEP 475 för motivering).

Exempel

Här är ett minimalt exempelprogram. Det använder funktionen alarm() för att begränsa väntetiden för att öppna en fil; detta är användbart om filen är för en seriell enhet som kanske inte är påslagen, vilket normalt skulle få os.open() att hänga sig på obestämd tid. Lösningen är att ställa in ett 5-sekunders alarm innan filen öppnas; om operationen tar för lång tid skickas alarmsignalen och hanteraren gör ett undantag.

import signal, os

def handler(signum, frame):
    signame = signal.Signals(signum).name
    print(f'Signal handler called with signal {signame} ({signum})')
    raise OSError("Couldn't open device!")

# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)

# This open() may hang indefinitely
fd = os.open('/dev/ttyS0', os.O_RDWR)

signal.alarm(0)          # Disable the alarm

Anmärkning om SIGPIPE

Att pipa utdata från ditt program till verktyg som head(1) kommer att orsaka att en SIGPIPE-signal skickas till din process när mottagaren av dess standardutdata stängs tidigt. Detta resulterar i ett undantag som BrokenPipeError: [Errno 32] Broken pipe. För att hantera det här fallet, linda in din ingångspunkt för att fånga det här undantaget enligt följande:

import os
import sys

def main():
    try:
        # simulate large output (your code replaces this loop)
        for x in range(10000):
            print("y")
        # flush output here to force SIGPIPE to be triggered
        # while inside this try block.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python exits with error code 1 on EPIPE

if __name__ == '__main__':
    main()

Ställ inte in SIGPIPE disposition till SIG_DFL för att undvika BrokenPipeError. Om du gör det kommer ditt program att avslutas oväntat när en socketanslutning avbryts medan ditt program fortfarande skriver till den.

Anmärkning om signalhanterare och undantag

Om en signalhanterare ger upphov till ett undantag, kommer undantaget att spridas till huvudtråden och kan ges upphov till efter varje bytecode-instruktion. Mest anmärkningsvärt är att ett KeyboardInterrupt kan dyka upp när som helst under exekveringen. Den mesta Python-koden, inklusive standardbiblioteket, kan inte göras robust mot detta, och därför kan ett KeyboardInterrupt (eller något annat undantag som härrör från en signalhanterare) vid sällsynta tillfällen försätta programmet i ett oväntat tillstånd.

För att illustrera detta problem kan du titta på följande kod:

class SpamContext:
    def __init__(self):
        self.lock = threading.Lock()

    def __enter__(self):
        # If KeyboardInterrupt occurs here, everything is fine
        self.lock.acquire()
        # If KeyboardInterrupt occurs here, __exit__ will not be called
        ...
        # KeyboardInterrupt could occur just before the function returns

    def __exit__(self, exc_type, exc_val, exc_tb):
        ...
        self.lock.release()

För många program, speciellt de som bara vill avsluta på KeyboardInterrupt, är detta inget problem, men applikationer som är komplexa eller kräver hög tillförlitlighet bör undvika att skapa undantag från signalhanterare. De bör också undvika att fånga KeyboardInterrupt som ett sätt att stänga av på ett elegant sätt. Istället bör de installera sin egen SIGINT-hanterare. Nedan följer ett exempel på en HTTP-server som undviker KeyboardInterrupt:

import signal
import socket
from selectors import DefaultSelector, EVENT_READ
from http.server import HTTPServer, SimpleHTTPRequestHandler

interrupt_read, interrupt_write = socket.socketpair()

def handler(signum, frame):
    print('Signal handler called with signal', signum)
    interrupt_write.send(b'\0')
signal.signal(signal.SIGINT, handler)

def serve_forever(httpd):
    sel = DefaultSelector()
    sel.register(interrupt_read, EVENT_READ)
    sel.register(httpd, EVENT_READ)

    while True:
        for key, _ in sel.select():
            if key.fileobj == interrupt_read:
                interrupt_read.recv(1)
                return
            if key.fileobj == httpd:
                httpd.handle_request()

print("Serving on port 8000")
httpd = HTTPServer(('', 8000), SimpleHTTPRequestHandler)
serve_forever(httpd)
print("Shutdown...")