signal — 비동기 이벤트에 대한 처리기 설정

Source code: Lib/signal.py


이 모듈은 파이썬에서 시그널 처리기를 사용하는 메커니즘을 제공합니다.

일반 규칙

signal.signal() 함수는 시그널이 수신될 때 실행될 사용자 정의 처리기를 정의하도록 합니다. 소수의 기본 처리기가 설치됩니다: SIGPIPE는 무시되고 (그래서 파이프와 소켓에 대한 쓰기 에러는 일반 파이썬 예외로 보고될 수 있습니다) SIGINT는 부모 프로세스가 변경하지 않았다면 KeyboardInterrupt 예외로 번역됩니다.

일단 설정되면, 특정 시그널에 대한 처리기는 명시적으로 재설정 될 때까지 (파이썬은 하부 구현과 관계없이 BSD 스타일 인터페이스를 흉내 냅니다) 설치된 상태로 유지됩니다. SIGCHLD에 대한 처리기는 예외인데, 하부 구현을 따릅니다.

On WebAssembly platforms wasm32-emscripten and wasm32-wasi, signals are emulated and therefore behave differently. Several functions and signals are not available on these platforms.

파이썬 시그널 처리기의 실행

파이썬 시그널 처리기는 저수준 (C) 시그널 처리기 내에서 실행되지 않습니다. 대신, 저수준 시그널 처리기는 가상 기계에게 나중에 (예를 들어 다음 바이트 코드 명령에서) 해당 파이썬 시그널 처리기를 실행하도록 지시하는 플래그를 설정합니다. 결과는 다음과 같습니다:

  • C 코드에서의 유효하지 않은 연산으로 인해 발생하는 SIGFPESIGSEGV와 같은 동기 에러를 잡는 것은 그리 의미가 없습니다. 파이썬은 시그널 처리기에서 C 코드로 돌아오는데, 같은 시그널을 다시 발생시켜서, 파이썬이 멈출 것입니다. 파이썬 3.3부터는, faulthandler 모듈을 사용하여 동기 에러를 보고할 수 있습니다.

  • C로만 구현된 오래 실행되는 계산(가령 커다란 텍스트 본문에 대한 정규식 일치)은 수신된 시그널에 상관없이 임의의 시간 동안 중단없이 실행될 수 있습니다. 계산이 끝나면 파이썬 시그널 처리기가 호출됩니다.

  • If the handler raises an exception, it will be raised “out of thin air” in the main thread. See the note below for a discussion.

시그널과 스레드

파이썬 시그널 처리기는 시그널이 다른 스레드에서 수신될 때도 항상 메인 인터프리터의 메인 파이썬 스레드에서 실행됩니다. 이는 시그널을 스레드 간 통신 수단으로 사용할 수 없음을 의미합니다. 대신 threading 모듈의 동기화 프리미티브를 사용할 수 있습니다.

게다가, 메인 인터프리터의 메인 스레드만 새로운 시그널 처리기를 설정할 수 있습니다.

모듈 내용

버전 3.5에서 변경: signal (SIG*), handler (SIG_DFL, SIG_IGN) and sigmask (SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK) related constants listed below were turned into enums (Signals, Handlers and Sigmasks respectively). getsignal(), pthread_sigmask(), sigpending() and sigwait() functions return human-readable enums as Signals objects.

The signal module defines three enums:

class signal.Signals

enum.IntEnum collection of SIG* constants and the CTRL_* constants.

버전 3.5에 추가.

class signal.Handlers

enum.IntEnum collection the constants SIG_DFL and SIG_IGN.

버전 3.5에 추가.

class signal.Sigmasks

enum.IntEnum collection the constants SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK.

가용성: 유닉스.

See the man page sigprocmask(2) and pthread_sigmask(3) for further information.

버전 3.5에 추가.

signal 모듈에 정의된 변수는 다음과 같습니다:

signal.SIG_DFL

이것은 두 가지 표준 시그널 처리 옵션 중 하나입니다; 단순히 시그널의 기본 기능을 수행합니다. 예를 들어, 대부분의 시스템에서 SIGQUIT의 기본 동작은 코어를 덤프하고 종료하는 것이지만, SIGCHLD의 기본 동작은 단순히 무시하는 것입니다.

signal.SIG_IGN

이것은 주어진 시그널을 무시하는 또 다른 표준 시그널 처리기입니다.

signal.SIGABRT

abort(3)로 부터의 중단 시그널.

signal.SIGALRM

alarm(2)로 부터의 타이머 시그널.

가용성: 유닉스.

signal.SIGBREAK

키보드 인터럽트 (CTRL + BREAK).

가용성: 윈도우.

signal.SIGBUS

버스 에러 (메모리 액세스 불량).

가용성: 유닉스.

signal.SIGCHLD

자식 프로세스가 중지되었거나 종료되었습니다.

가용성: 유닉스.

signal.SIGCLD

SIGCHLD에 대한 별칭.

Availability: not macOS.

signal.SIGCONT

현재 중지되었으면 프로세스를 재개합니다.

가용성: 유닉스.

signal.SIGFPE

부동 소수점 예외. 예를 들어, 0으로 나누기.

더 보기

나누기나 모듈로 연산의 두 번째 인자가 0이면 ZeroDivisionError가 발생합니다.

signal.SIGHUP

제어 터미널이 끊어졌거나 제어 프로세스가 죽었습니다.

가용성: 유닉스.

signal.SIGILL

잘못된 명령어.

signal.SIGINT

키보드 인터럽트 (CTRL + C).

기본 액션은 KeyboardInterrupt를 발생시키는 것입니다.

signal.SIGKILL

킬 시그널.

잡거나, 차단하거나, 무시할 수 없습니다.

가용성: 유닉스.

signal.SIGPIPE

끊어진 파이프: 판독기가 없는 파이프에 쓰기.

기본 동작은 시그널을 무시하는 것입니다.

가용성: 유닉스.

signal.SIGSEGV

세그먼테이션 오류: 유효하지 않은 메모리 참조.

signal.SIGSTKFLT

Stack fault on coprocessor. The Linux kernel does not raise this signal: it can only be raised in user space.

Availability: Linux.

On architectures where the signal is available. See the man page signal(7) for further information.

버전 3.11에 추가.

signal.SIGTERM

종료 시그널.

signal.SIGUSR1

사용자 정의 시그널 1.

가용성: 유닉스.

signal.SIGUSR2

사용자 정의 시그널 2.

가용성: 유닉스.

signal.SIGWINCH

창 크기 조정 시그널.

가용성: 유닉스.

SIG*

모든 시그널 번호는 기호적으로 정의됩니다. 예를 들어, 행업(hangup) 시그널은 signal.SIGHUP으로 정의됩니다; 변수 이름은 <signal.h>에 있는 C 프로그램에서 사용되는 이름과 동일합니다. ‘signal()'에 대한 유닉스 매뉴얼 페이지는 존재하는 시그널을 나열합니다 (일부 시스템에서는 signal(2)이고, 다른 시스템에서는 signal(7)입니다). 모든 시스템이 같은 시그널 이름 집합을 정의하는 것은 아님에 유의하십시오; 시스템에서 정의한 이름만 이 모듈에서 정의합니다.

signal.CTRL_C_EVENT

Ctrl+C 키 입력 이벤트에 해당하는 시그널. 이 시그널은 os.kill()에서만 사용할 수 있습니다.

가용성: 윈도우.

버전 3.2에 추가.

signal.CTRL_BREAK_EVENT

Ctrl+Break 키 입력 이벤트에 해당하는 시그널. 이 시그널은 os.kill()에서만 사용할 수 있습니다.

가용성: 윈도우.

버전 3.2에 추가.

signal.NSIG

One more than the number of the highest signal number. Use valid_signals() to get valid signal numbers.

signal.ITIMER_REAL

간격 타이머(interval timer)를 실시간으로 감소시키고, 만료 시 SIGALRM을 전달합니다.

signal.ITIMER_VIRTUAL

프로세스가 실행 중일 때만 간격 타이머(interval timer)를 감소시키고, 만료 시 SIGVTALRM을 전달합니다.

signal.ITIMER_PROF

프로세스가 실행될 때와 시스템이 프로세스를 대신하여 실행될 때 간격 타이머(interval timer)를 감소시킵니다. ITIMER_VIRTUAL과 함께 사용되어, 이 타이머는 일반적으로 사용자와 커널 공간에서 응용 프로그램이 소비한 시간을 프로파일링하는 데 사용됩니다. 만료 시 SIGPROF를 전달합니다.

signal.SIG_BLOCK

pthread_sigmask()how 매개 변수에 가능한 값으로 시그널이 차단됨을 나타냅니다.

버전 3.3에 추가.

signal.SIG_UNBLOCK

pthread_sigmask()how 매개 변수에 가능한 값으로 시그널이 차단 해제됨을 나타냅니다.

버전 3.3에 추가.

signal.SIG_SETMASK

pthread_sigmask()how 매개 변수에 가능한 값으로 시그널 마스크가 교체됨을 나타냅니다.

버전 3.3에 추가.

signal 모듈은 하나의 예외를 정의합니다:

exception signal.ItimerError

하부 setitimer()getitimer() 구현으로부터의 에러를 알리기 위해 발생합니다. 유효하지 않은 간격 타이머나 음의 시간이 setitimer()에 전달되면 이 에러가 예상됩니다. 이 에러는 OSError의 서브 형입니다.

버전 3.3에 추가: 이 에러는 IOError의 서브 형이었습니다, 이제는 OSError의 별칭입니다.

signal 모듈은 다음 함수를 정의합니다:

signal.alarm(time)

time이 0이 아니면, 이 함수는 SIGALRM 시그널이 time 초 내에 프로세스로 전송되도록 요청합니다. 이전에 예약된 알람은 취소됩니다 (임의의 시간에 오직 하나의 알람만 예약될 수 있습니다). 반환된 값은 이전에 설정된 알람이 전달되기까지 남은 초(seconds)입니다. time이 0이면, 알람이 예약되지 않고, 예약된 알람이 취소됩니다. 반환 값이 0이면, 현재 예약된 알람이 없습니다.

가용성: 유닉스.

See the man page alarm(2) for further information.

signal.getsignal(signalnum)

시그널 signalnum에 대한 현재 시그널 처리기를 반환합니다. 반환된 값은 콜러블 파이썬 객체이거나, 특수 값 signal.SIG_IGN, signal.SIG_DFL 중 하나이거나 None일 수 있습니다. 여기서 signal.SIG_IGN은 시그널이 이전에 무시되었음을 의미하고, signal.SIG_DFL은 시그널을 처리하는 기본 방법이 이전에 사용 중임을 의미하고, None은 이전 시그널 처리기가 파이썬에서 설치되지 않았음을 의미합니다.

signal.strsignal(signalnum)

Returns the description of signal signalnum, such as “Interrupt” for SIGINT. Returns None if signalnum has no description. Raises ValueError if signalnum is invalid.

버전 3.8에 추가.

signal.valid_signals()

이 플랫폼에서 유효한 시그널 번호 집합을 반환합니다. 일부 시그널이 시스템에서 내부 용으로 예약되었으면 range(1, NSIG)보다 작을 수 있습니다.

버전 3.8에 추가.

signal.pause()

시그널이 수신될 때까지 프로세스를 휴면 상태로 만듭니다; 그런 다음 적절한 처리기가 호출됩니다. 아무것도 반환하지 않습니다.

가용성: 유닉스.

See the man page signal(2) for further information.

sigwait(), sigwaitinfo(), sigtimedwait()sigpending()도 참조하십시오.

signal.raise_signal(signum)

호출하는 프로세스에 시그널을 보냅니다. 아무것도 반환하지 않습니다.

버전 3.8에 추가.

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

파일 기술자 pidfd가 참조하는 프로세스로 시그널 sig를 보냅니다. 파이썬은 현재 siginfo 매개 변수를 지원하지 않습니다; None이어야 합니다. flags 인자는 향후 확장을 위해 제공됩니다; 현재는 플래그 값이 정의되어 있지 않습니다.

자세한 내용은 pidfd_send_signal(2) 매뉴얼 페이지를 참조하십시오.

Availability: Linux >= 5.1

버전 3.9에 추가.

signal.pthread_kill(thread_id, signalnum)

시그널 signalnum을 호출자와 같은 프로세스의 다른 스레드인 스레드 thread_id로 보냅니다. 대상 스레드는 임의의 (파이썬이거나 아닌) 코드를 실행 중일 수 있습니다. 그러나, 대상 스레드가 파이썬 인터프리터를 실행 중이면, 파이썬 시그널 처리기는 메인 인터프리터의 메인 스레드에서 실행됩니다. 따라서, 특정 파이썬 스레드에 시그널을 보내는 것의 유일한 용도는 실행 중인 시스템 호출이 InterruptedError로 실패하도록 하는 것입니다.

thread_id에 적합한 값을 얻으려면 threading.get_ident()threading.Thread 객체의 ident 어트리뷰트를 사용하십시오.

signalnum이 0이면, 시그널이 전송되지 않지만, 여전히 에러 검사가 수행됩니다; 대상 스레드가 여전히 실행 중인지 확인하는 데 사용할 수 있습니다.

인자 thread_id, signalnum으로 감사 이벤트 signal.pthread_kill을 발생시킵니다.

가용성: 유닉스.

See the man page pthread_kill(3) for further information.

os.kill()도 참조하십시오.

버전 3.3에 추가.

signal.pthread_sigmask(how, mask)

호출하는 스레드의 시그널 마스크를 가져오거나 변경하거나 가져오면서 변경합니다. 시그널 마스크는 호출자에게 현재 배달이 차단된 시그널 집합입니다. 이전 시그널 마스크를 시그널 집합으로 반환합니다.

호출의 동작은 다음과 같이 how 값에 따라 다릅니다.

  • SIG_BLOCK: 차단된 시그널 집합은 현재 집합과 mask 인자의 합집합입니다.

  • SIG_UNBLOCK: mask에 있는 시그널이 차단된 시그널의 현재 집합에서 제거됩니다. 차단되지 않은 시그널을 차단 해제하려고 시도할 수 있습니다.

  • SIG_SETMASK: 차단된 시그널 집합이 mask 인자로 설정됩니다.

mask는 시그널 번호 집합입니다 (예를 들어 {signal.SIGINT, signal.SIGTERM}). 모든 시그널을 포함한 전체 마스크를 얻으려면 valid_signals()를 사용하십시오.

예를 들어, signal.pthread_sigmask(signal.SIG_BLOCK, [])은 호출하는 스레드의 시그널 마스크를 읽습니다.

SIGKILLSIGSTOP은 차단할 수 없습니다.

가용성: 유닉스.

See the man page sigprocmask(2) and pthread_sigmask(3) for further information.

pause(), sigpending()sigwait()도 참조하십시오.

버전 3.3에 추가.

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

seconds(alarm()과 달리 float가 허용됩니다) 이후에 그리고 (interval이 0이 아니면) 그 후로 interval 초마다 발사(fire)하도록 which로 지정된 간격 타이머(signal.ITIMER_REAL, signal.ITIMER_VIRTUAL 또는 signal.ITIMER_PROF 중 하나)를 설정합니다. which로 지정된 간격 타이머는 seconds를 0으로 설정하여 지울 수 있습니다.

간격 타이머가 발사(fire)하면, 시그널이 프로세스로 전송됩니다. 전송된 시그널은 사용 중인 타이머에 따라 다릅니다; signal.ITIMER_REALSIGALRM을, signal.ITIMER_VIRTUALSIGVTALRM을, signal.ITIMER_PROFSIGPROF를 전달합니다.

이전 값은 튜플로 반환됩니다: (지연, 간격).

유효하지 않은 간격 타이머를 전달하려고 하면 ItimerError가 발생합니다.

가용성: 유닉스.

signal.getitimer(which)

which로 지정된 주어진 간격 타이머의 현재 값을 반환합니다.

가용성: 유닉스.

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

웨이크업 파일 기술자를 fd로 설정합니다. 시그널이 수신되면 시그널 번호는 단일 바이트로 fd에 기록됩니다. 라이브러리에서 poll이나 select 호출을 깨워서, 시그널을 완전히 처리하는 데 사용될 수 있습니다.

이전 웨이크업 fd가 반환됩니다 (또는 파일 기술자 웨이크업이 활성화되지 않았으면 -1). fd가 -1이면, 파일 기술자 웨이크업이 비활성화됩니다. -1이 아니면, fd는 비 블로킹이어야 합니다. poll이나 select를 다시 호출하기 전에 fd에서 바이트를 제거하는 것은 라이브러리의 책임입니다.

스레드가 활성화되었을 때, 이 함수는 메인 인터프리터의 메인 스레드에서만 호출할 수 있습니다; 다른 스레드에서 호출하려고 하면 ValueError 예외가 발생합니다.

이 함수를 사용하는 일반적인 두 가지 방법이 있습니다. 두 방법 모두, 시그널이 도착할 때 깨어나기 위해 fd를 사용하지만, 어떤 시그널이나 시그널들이 도착했는지 판단하는 방법이 다릅니다.

첫 번째 방법에서는, fd의 버퍼에서 데이터를 읽고, 바이트 값이 시그널 번호를 제공합니다. 이것은 간단합니다만, 드물게 문제가 될 수 있습니다: 일반적으로 fd에는 제한된 버퍼 공간이 있으며, 너무 많은 시그널이 너무 빨리 도착하면, 버퍼가 가득 차고, 일부 시그널이 손실될 수 있습니다. 이 방법을 사용하면, 시그널이 손실될 때 최소한 stderr에 경고가 인쇄되도록 warn_on_full_buffer=True를 설정해야 합니다.

두 번째 방법에서는, 오직 웨이크업만을 위해 웨이크업 fd를 사용하고, 실제 바이트 값은 무시합니다. 이 경우, 우리가 신경 쓰는 것은 fd의 버퍼가 비어 있는지 비어 있지 않은지 입니다; 가득 찬 버퍼는 전혀 문제를 가리키지 않습니다. 이 방법을 사용하면, 사용자가 가짜 경고 메시지로 혼동되지 않도록 warn_on_full_buffer=False를 설정해야 합니다.

버전 3.5에서 변경: 윈도우에서, 이 함수는 이제 소켓 핸들도 지원합니다.

버전 3.7에서 변경: warn_on_full_buffer 매개 변수를 추가했습니다.

signal.siginterrupt(signalnum, flag)

시스템 호출 재시작 동작을 변경합니다: flagFalse이면, 시그널 signalnum에 의해 인터럽트 될 때 시스템 호출이 다시 시작되고, 그렇지 않으면 시스템 호출이 중단됩니다. 아무것도 반환하지 않습니다.

가용성: 유닉스.

See the man page siginterrupt(3) for further information.

Note that installing a signal handler with signal() will reset the restart behaviour to interruptible by implicitly calling siginterrupt() with a true flag value for the given signal.

signal.signal(signalnum, handler)

시그널 signalnum의 처리기를 함수 handler로 설정합니다. handler는 두 개의 인자(아래를 참조하십시오)를 취하는 콜러블 파이썬 객체, 또는 특수 값 signal.SIG_IGN이나 signal.SIG_DFL 중 하나일 수 있습니다. 이전 시그널 처리기가 반환됩니다 (위의 getsignal() 설명을 참조하십시오). (자세한 내용은 유닉스 매뉴얼 페이지 signal(2)를 참조하십시오.)

스레드가 활성화되었을 때, 이 함수는 메인 인터프리터의 메인 스레드에서만 호출할 수 있습니다; 다른 스레드에서 호출하려고 하면 ValueError 예외가 발생합니다.

handler는 두 개의 인자로 호출됩니다: 시그널 번호와 현재 스택 프레임 (None이나 프레임 객체; 프레임 객체에 대한 설명은, 형 계층에 있는 설명을 참조하거나 inspect 모듈의 어트리뷰트 설명을 참조하십시오).

윈도우에서, signal()SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM 또는 SIGBREAK로만 호출 할 수 있습니다. 다른 경우에는 ValueError가 발생합니다. 모든 시스템이 같은 시그널 이름 집합을 정의하는 것은 아님에 유의하십시오; 시그널 이름이 SIG* 모듈 수준 상수로 정의되지 않으면 AttributeError가 발생합니다.

signal.sigpending()

호출하는 스레드로 전달 계류 중인 시그널 집합을 검사합니다 (즉, 차단된 동안 발생한 시그널). 계류 중인 시그널 집합을 반환합니다.

가용성: 유닉스.

See the man page sigpending(2) for further information.

pause(), pthread_sigmask()sigwait()도 참조하십시오.

버전 3.3에 추가.

signal.sigwait(sigset)

시그널 집합 sigset에 지정된 시그널 중 하나가 전달될 때까지 호출하는 스레드의 실행을 일시 중단합니다. 이 함수는 시그널을 받아들이고 (계류 중인 시그널 목록에서 제거합니다), 시그널 번호를 반환합니다.

가용성: 유닉스.

See the man page sigwait(3) for further information.

pause(), pthread_sigmask(), sigpending(), sigwaitinfo()sigtimedwait()도 참조하십시오.

버전 3.3에 추가.

signal.sigwaitinfo(sigset)

시그널 집합 sigset에 지정된 시그널 중 하나가 전달될 때까지 호출하는 스레드의 실행을 일시 중단합니다. 이 함수는 시그널을 받아들이고 계류 중인 시그널 목록에서 제거합니다. sigset의 시그널 중 하나가 이미 호출하는 스레드에 대해 계류 중이면, 함수는 해당 시그널에 대한 정보와 함께 즉시 반환합니다. 전달된 시그널에 대해 시그널 처리기가 호출되지 않습니다. 이 함수는 sigset에 없는 시그널에 의해 중단되면 InterruptedError를 발생시킵니다.

반환 값은 siginfo_t 구조체에 포함된 데이터, 즉 si_signo, si_code, si_errno, si_pid, si_uid, si_status, si_band를 표현하는 객체입니다.

가용성: 유닉스.

See the man page sigwaitinfo(2) for further information.

pause(), sigwait()sigtimedwait()도 참조하십시오.

버전 3.3에 추가.

버전 3.5에서 변경: 이 함수는 이제 sigset에 없는 시그널에 의해 중단되고 시그널 처리기가 예외를 발생시키지 않으면 재시도됩니다 (이유는 PEP 475를 참조하십시오).

signal.sigtimedwait(sigset, timeout)

Like sigwaitinfo(), but takes an additional timeout argument specifying a timeout. If timeout is specified as 0, a poll is performed. Returns None if a timeout occurs.

가용성: 유닉스.

See the man page sigtimedwait(2) for further information.

pause(), sigwait()sigwaitinfo()도 참조하십시오.

버전 3.3에 추가.

버전 3.5에서 변경: 이 함수는 이제 sigset에 없는 시그널에 의해 중단되고 시그널 처리기가 예외를 발생시키지 않으면 다시 계산된 timeout으로 재시도됩니다 (이유는 PEP 475를 참조하십시오).

Examples

다음은 최소한의 예제 프로그램입니다. alarm() 함수를 사용하여 파일을 여는 데 대기하는 시간을 제한합니다; 이것은 파일이 켜져 있지 않을 수 있는 직렬 장치를 위한 파일일 때 유용하며, 일반적으로 os.open()이 무기한 정지됩니다. 해결책은 파일을 열기 전에 5초 알람을 설정하는 것입니다; 작업이 너무 오래 걸리면, 알람 시그널이 전송되고, 처리기가 예외를 발생시킵니다.

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

SIGPIPE에 대한 참고 사항

프로그램의 출력을 head(1)와 같은 도구로 파이핑 하면 표준 출력의 수신기가 일찍 닫힐 때 여러분의 프로세스로 SIGPIPE 시그널이 전송됩니다. 이것은 BrokenPipeError: [Errno 32] Broken pipe와 같은 예외를 일으킵니다. 이 경우를 처리하려면, 다음과 같이 이 예외를 포착하도록 진입점을 감싸십시오:

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()

Do not set SIGPIPE’s disposition to SIG_DFL in order to avoid BrokenPipeError. Doing that would cause your program to exit unexpectedly whenever any socket connection is interrupted while your program is still writing to it.

Note on Signal Handlers and Exceptions

If a signal handler raises an exception, the exception will be propagated to the main thread and may be raised after any bytecode instruction. Most notably, a KeyboardInterrupt may appear at any point during execution. Most Python code, including the standard library, cannot be made robust against this, and so a KeyboardInterrupt (or any other exception resulting from a signal handler) may on rare occasions put the program in an unexpected state.

To illustrate this issue, consider the following code:

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()

For many programs, especially those that merely want to exit on KeyboardInterrupt, this is not a problem, but applications that are complex or require high reliability should avoid raising exceptions from signal handlers. They should also avoid catching KeyboardInterrupt as a means of gracefully shutting down. Instead, they should install their own SIGINT handler. Below is an example of an HTTP server that avoids 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...")