signal
--- 設(shè)置異步事件處理程序?
該模塊提供了在 Python 中使用信號(hào)處理程序的機(jī)制。
一般規(guī)則?
signal.signal()
函數(shù)允許定義在接收到信號(hào)時(shí)執(zhí)行的自定義處理程序。少量的默認(rèn)處理程序已經(jīng)設(shè)置: SIGPIPE
被忽略(因此管道和套接字上的寫入錯(cuò)誤可以報(bào)告為普通的 Python 異常)以及如果父進(jìn)程沒有更改 SIGINT
,則其會(huì)被翻譯成 KeyboardInterrupt
異常。
一旦設(shè)置,特定信號(hào)的處理程序?qū)⒈3职惭b,直到它被顯式重置( Python 模擬 BSD 樣式接口而不管底層實(shí)現(xiàn)),但 SIGCHLD
的處理程序除外,它遵循底層實(shí)現(xiàn)。
執(zhí)行 Python 信號(hào)處理程序?
Python 信號(hào)處理程序不會(huì)在低級(jí)( C )信號(hào)處理程序中執(zhí)行。相反,低級(jí)信號(hào)處理程序設(shè)置一個(gè)標(biāo)志,告訴 virtual machine 稍后執(zhí)行相應(yīng)的 Python 信號(hào)處理程序(例如在下一個(gè) bytecode 指令)。這會(huì)導(dǎo)致:
捕獲同步錯(cuò)誤是沒有意義的,例如
SIGFPE
或SIGSEGV
,它們是由 C 代碼中的無效操作引起的。Python 將從信號(hào)處理程序返回到 C 代碼,這可能會(huì)再次引發(fā)相同的信號(hào),導(dǎo)致 Python 顯然的掛起。 從Python 3.3開始,你可以使用faulthandler
模塊來報(bào)告同步錯(cuò)誤。純 C 中實(shí)現(xiàn)的長(zhǎng)時(shí)間運(yùn)行的計(jì)算(例如在大量文本上的正則表達(dá)式匹配)可以在任意時(shí)間內(nèi)不間斷地運(yùn)行,而不管接收到任何信號(hào)。計(jì)算完成后將調(diào)用 Python 信號(hào)處理程序。
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.
信號(hào)與線程?
Python 信號(hào)處理程序總是會(huì)在主 Python 主解釋器的主線程中執(zhí)行,即使信號(hào)是在另一個(gè)線程中接收的。 這意味著信號(hào)不能被用作線程間通信的手段。 你可以改用 threading
模塊中的同步原語。
此外,只有主解釋器的主線程才被允許設(shè)置新的信號(hào)處理程序。
模塊內(nèi)容?
在 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 constantsSIG_DFL
andSIG_IGN
.3.5 新版功能.
- class signal.Sigmasks?
enum.IntEnum
collection the constantsSIG_BLOCK
,SIG_UNBLOCK
andSIG_SETMASK
.Availability: Unix. See the man page sigprocmask(3) and pthread_sigmask(3) for further information.
3.5 新版功能.
在 signal
模塊中定義的變量是:
- signal.SIG_DFL?
這是兩種標(biāo)準(zhǔn)信號(hào)處理選項(xiàng)之一;它只會(huì)執(zhí)行信號(hào)的默認(rèn)函數(shù)。 例如,在大多數(shù)系統(tǒng)上,對(duì)于
SIGQUIT
的默認(rèn)操作是轉(zhuǎn)儲(chǔ)核心并退出,而對(duì)于SIGCHLD
的默認(rèn)操作是簡(jiǎn)單地忽略它。
- signal.SIG_IGN?
這是另一個(gè)標(biāo)準(zhǔn)信號(hào)處理程序,它將簡(jiǎn)單地忽略給定的信號(hào)。
- signal.SIGFPE?
浮點(diǎn)異常。 例如除以零。
參見
當(dāng)除法或求余運(yùn)算的第二個(gè)參數(shù)為零時(shí)會(huì)引發(fā)
ZeroDivisionError
。
- signal.SIGILL?
非法指令。
- signal.SIGINT?
來自鍵盤的中斷 (CTRL + C)。
默認(rèn)的動(dòng)作是引發(fā)
KeyboardInterrupt
。
- signal.SIGSEGV?
段錯(cuò)誤:無效的內(nèi)存引用。
- 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?
終結(jié)信號(hào)。
- SIG*
所有信號(hào)編號(hào)都是符號(hào)化定義的。 例如,掛起信號(hào)被定義為
signal.SIGHUP
;變量的名稱與 C 程序中使用的名稱相同,具體見<signal.h>
。 'signal()
' 的 Unix 手冊(cè)頁面列出了現(xiàn)有的信號(hào) (在某些系統(tǒng)上這是 signal(2),在其他系統(tǒng)中此列表則是在 signal(7) 中)。 請(qǐng)注意并非所有系統(tǒng)都會(huì)定義相同的信號(hào)名稱集;只有系統(tǒng)所定義的名稱才會(huì)由此模塊來定義。
- signal.CTRL_C_EVENT?
對(duì)應(yīng)于 Ctrl+C 擊鍵事件的信號(hào)。此信號(hào)只能用于
os.kill()
。可用性: Windows。
3.2 新版功能.
- signal.CTRL_BREAK_EVENT?
對(duì)應(yīng)于 Ctrl+Break 擊鍵事件的信號(hào)。此信號(hào)只能用于
os.kill()
。可用性: Windows。
3.2 新版功能.
- signal.NSIG?
One more than the number of the highest signal number. Use
valid_signals()
to get valid signal numbers.
- signal.ITIMER_VIRTUAL?
僅在進(jìn)程執(zhí)行時(shí)遞減間隔計(jì)時(shí)器,并在到期時(shí)發(fā)送 SIGVTALRM 。
- signal.ITIMER_PROF?
當(dāng)進(jìn)程執(zhí)行時(shí)以及當(dāng)系統(tǒng)替進(jìn)程執(zhí)行時(shí)都會(huì)減小間隔計(jì)時(shí)器。 這個(gè)計(jì)時(shí)器與 ITIMER_VIRTUAL 相配結(jié),通常被用于分析應(yīng)用程序在用戶和內(nèi)核空間中花費(fèi)的時(shí)間。 SIGPROF 會(huì)在超期時(shí)被發(fā)送。
- signal.SIG_BLOCK?
pthread_sigmask()
的 how 形參的一個(gè)可能的值,表明信號(hào)將會(huì)被阻塞。3.3 新版功能.
- signal.SIG_UNBLOCK?
pthread_sigmask()
的 how 形參的是個(gè)可能的值,表明信號(hào)將被解除阻塞。3.3 新版功能.
- signal.SIG_SETMASK?
pthread_sigmask()
的 how 形參的一個(gè)可能的值,表明信號(hào)掩碼將要被替換。3.3 新版功能.
signal
模塊定義了一個(gè)異常:
- exception signal.ItimerError?
作為來自下層
setitimer()
或getitimer()
實(shí)現(xiàn)錯(cuò)誤的信號(hào)被引發(fā)。 如果將無效的定時(shí)器或負(fù)的時(shí)間值傳給setitimer()
就導(dǎo)致這個(gè)錯(cuò)誤。 此錯(cuò)誤是OSError
的子類型。
signal
模塊定義了以下函數(shù):
- signal.alarm(time)?
如果 time 值非零,則此函數(shù)將要求將一個(gè)
SIGALRM
信號(hào)在 time 秒內(nèi)發(fā)往進(jìn)程。 任何在之前排入計(jì)劃的警報(bào)都會(huì)被取消(在任何時(shí)刻都只能有一個(gè)警報(bào)被排入計(jì)劃)。 后續(xù)的返回值將是任何之前設(shè)置的警報(bào)被傳入之前的秒數(shù)。 如果 time 值為零,則不會(huì)將任何警報(bào)排入計(jì)劃,并且任何已排入計(jì)劃的警報(bào)都會(huì)被取消。 如果返回值為零,則目前沒有任何警報(bào)被排入計(jì)劃。
- signal.getsignal(signalnum)?
返回當(dāng)前用于信號(hào) signalnum 的信號(hào)處理程序。 返回值可以是一個(gè) Python 可調(diào)用對(duì)象,或是特殊值
signal.SIG_IGN
,signal.SIG_DFL
或None
之一。 在這里,signal.SIG_IGN
表示信號(hào)在之前被忽略,signal.SIG_DFL
表示之前在使用默認(rèn)的信號(hào)處理方式,而None
表示之前的信號(hào)處理程序未由 Python 安裝。
- signal.strsignal(signalnum)?
返回信號(hào) signalnum 的系統(tǒng)描述,例如 "Interrupt", "Segmentation fault" 等等。 如果信號(hào)無法被識(shí)別則返回
None
。3.8 新版功能.
- signal.valid_signals()?
返回本平臺(tái)上的有效信號(hào)編號(hào)集。 這可能會(huì)少于
range(1, NSIG)
,如果某些信號(hào)被系統(tǒng)保留作為內(nèi)部使用的話。3.8 新版功能.
- signal.pause()?
使進(jìn)程休眠直至接收到一個(gè)信號(hào);然后將會(huì)調(diào)用適當(dāng)?shù)奶幚沓绦颉?返回空值。
可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 signal(2)。
另請(qǐng)參閱
sigwait()
,sigwaitinfo()
,sigtimedwait()
和sigpending()
。
- signal.raise_signal(signum)?
向調(diào)用方進(jìn)程發(fā)送一個(gè)信號(hào)。 返回空值。
3.8 新版功能.
- signal.pidfd_send_signal(pidfd, sig, siginfo=None, flags=0)?
發(fā)送信號(hào) sig 到文件描述符 pidfd 所指向的進(jìn)程。 Python 目前不支持 siginfo 形參;它必須為
None
。 提供 flags 參數(shù)是為了將來擴(kuò)展;當(dāng)前未定義旗標(biāo)值。更多信息請(qǐng)參閱 pidfd_send_signal(2) 手冊(cè)頁面。
可用性: Linux 5.1+
3.9 新版功能.
- signal.pthread_kill(thread_id, signalnum)?
將信號(hào) signalnum 發(fā)送至與調(diào)用者在同一進(jìn)程中另一線程 thread_id。 目標(biāo)線程可被用于執(zhí)行任何代碼(Python或其它)。 但是,如果目標(biāo)線程是在執(zhí)行 Python 解釋器,則 Python 信號(hào)處理程序?qū)?由主解釋器的主線程來執(zhí)行。 因此,將信號(hào)發(fā)送給特定 Python 線程的唯一作用在于強(qiáng)制讓一個(gè)正在運(yùn)行的系統(tǒng)調(diào)用失敗并拋出
InterruptedError
。使用
threading.get_ident()
或threading.Thread
對(duì)象的ident
屬性為 thread_id 獲取合適的值。如果 signalnum 為 0,則不會(huì)發(fā)送信號(hào),但仍然會(huì)執(zhí)行錯(cuò)誤檢測(cè);這可被用來檢測(cè)目標(biāo)線程是否仍在運(yùn)行。
引發(fā)一個(gè) 審計(jì)事件
signal.pthread_kill
,附帶參數(shù)thread_id
,signalnum
。可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 pthread_kill(3)。
另請(qǐng)參閱
os.kill()
。3.3 新版功能.
- signal.pthread_sigmask(how, mask)?
獲取和/或修改調(diào)用方線程的信號(hào)掩碼。 信號(hào)掩碼是一組傳送過程目前為調(diào)用者而阻塞的信號(hào)集。 返回舊的信號(hào)掩碼作為一組信號(hào)。
該調(diào)用的行為取決于 how 的值,具體見下。
SIG_BLOCK
: 被阻塞信號(hào)集是當(dāng)前集與 mask 參數(shù)的并集。SIG_UNBLOCK
: mask 中的信號(hào)會(huì)從當(dāng)前已阻塞信號(hào)集中被移除。 允許嘗試取消對(duì)一個(gè)非阻塞信號(hào)的阻塞。SIG_SETMASK
: 已阻塞信號(hào)集會(huì)被設(shè)為 mask 參數(shù)的值。
mask 是一個(gè)信號(hào)編號(hào)集合 (例如 {
signal.SIGINT
,signal.SIGTERM
})。 請(qǐng)使用valid_signals()
表示包含所有信號(hào)的完全掩碼。例如,
signal.pthread_sigmask(signal.SIG_BLOCK, [])
會(huì)讀取調(diào)用方線程的信號(hào)掩碼。SIGKILL
和SIGSTOP
不能被阻塞。可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 sigprocmask(2) 和 pthread_sigmask(3)。
另請(qǐng)參閱
pause()
,sigpending()
和sigwait()
。3.3 新版功能.
- signal.setitimer(which, seconds, interval=0.0)?
設(shè)置由 which 指明的給定間隔計(jì)時(shí)器 (
signal.ITIMER_REAL
,signal.ITIMER_VIRTUAL
或signal.ITIMER_PROF
之一) 在 seconds 秒 (接受浮點(diǎn)數(shù)值,為與alarm()
之差) 之后開始并在每 interval 秒間隔時(shí) (如果 interval 不為零) 啟動(dòng)。 由 which 指明的間隔計(jì)時(shí)器可通過將 seconds 設(shè)為零來清空。當(dāng)一個(gè)間隔計(jì)時(shí)器啟動(dòng)時(shí),會(huì)有信號(hào)發(fā)送至進(jìn)程。 所發(fā)送的具體信號(hào)取決于所使用的計(jì)時(shí)器;
signal.ITIMER_REAL
將發(fā)送SIGALRM
,signal.ITIMER_VIRTUAL
將發(fā)送SIGVTALRM
, 而signal.ITIMER_PROF
將發(fā)送SIGPROF
.原有的值會(huì)以元組: (delay, interval) 的形式被返回。
嘗試傳入無效的計(jì)時(shí)器將導(dǎo)致
ItimerError
。可用性: Unix。
- signal.set_wakeup_fd(fd, *, warn_on_full_buffer=True)?
將喚醒文件描述符設(shè)為 fd。 當(dāng)接收到信號(hào)時(shí),會(huì)將信號(hào)編號(hào)以單個(gè)字節(jié)的形式寫入 fd。 這可被其它庫用來喚醒一次 poll 或 select 調(diào)用,以允許該信號(hào)被完全地處理。
原有的喚醒 fd 會(huì)被返回(或者如果未啟用文件描述符喚醒則返回 -1)。 如果 fd 為 -1,文件描述符喚醒會(huì)被禁用。 如果不為 -1,則 fd 必須為非阻塞型。 需要由庫來負(fù)責(zé)在重新調(diào)用 poll 或 select 之前從 fd 移除任何字節(jié)數(shù)據(jù)。
當(dāng)啟用線程用時(shí),此函數(shù)只能從 主解釋器的主線程 被調(diào)用;嘗試從另一線程調(diào)用它將導(dǎo)致
ValueError
異常被引發(fā)。使用此函數(shù)有兩種通常的方式。 在兩種方式下,當(dāng)有信號(hào)到達(dá)時(shí)你都是用 fd 來喚醒,但之后它們?cè)诖_定達(dá)到的一個(gè)或多個(gè)信號(hào) which 時(shí)存在差異。
在第一種方式下,我們從 fd 的緩沖區(qū)讀取數(shù)據(jù),這些字節(jié)值會(huì)給你信號(hào)編號(hào)。 這種方式很簡(jiǎn)單,但在少數(shù)情況下會(huì)發(fā)生問題:通常 fd 將有緩沖區(qū)空間大小限制,如果信號(hào)到達(dá)得太多且太快,緩沖區(qū)可能會(huì)爆滿,有些信號(hào)可能丟失。 如果你使用此方式,則你應(yīng)當(dāng)設(shè)置
warn_on_full_buffer=True
,當(dāng)信號(hào)丟失時(shí)這至少能將警告消息打印到 stderr。在第二種方式下,我們 只會(huì) 將喚醒 fd 用于喚醒,而忽略實(shí)際的字節(jié)值。 在此情況下,我們所關(guān)心的只有 fd 的緩沖區(qū)為空還是不為空;爆滿的緩沖區(qū)完全不會(huì)導(dǎo)致問題。 如果你使用此方式,則你應(yīng)當(dāng)設(shè)置
warn_on_full_buffer=False
,這樣你的用戶就不會(huì)被虛假的警告消息所迷惑。在 3.5 版更改: 在 Windows 上,此函數(shù)現(xiàn)在也支持套接字處理。
在 3.7 版更改: 添加了
warn_on_full_buffer
形參。
- signal.siginterrupt(signalnum, flag)?
更改系統(tǒng)調(diào)用重啟行為:如果 flag 為
False
,系統(tǒng)調(diào)用將在被信號(hào) signalnum 中斷時(shí)重啟,否則系統(tǒng)調(diào)用將被中斷。 返回空值。可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 siginterrupt(3)。
請(qǐng)注意用
signal()
安裝信號(hào)處理程序?qū)⒅貑⑿袨橹刂脼榭赏ㄟ^顯式調(diào)用siginterrupt()
并為給定信號(hào)的 flag 設(shè)置真值來實(shí)現(xiàn)中斷。
- signal.signal(signalnum, handler)?
將信號(hào) signalnum 的處理程序設(shè)為函數(shù) handler。 handler 可以為接受兩個(gè)參數(shù)(見下)的 Python 可調(diào)用對(duì)象,或者為特殊值
signal.SIG_IGN
或signal.SIG_DFL
之一。 之前的信號(hào)處理程序?qū)⒈环祷兀▍⒁娚衔?getsignal()
的描述)。 (更多信息請(qǐng)參閱 Unix 手冊(cè)頁面 signal(2)。)當(dāng)啟用線程用時(shí),此函數(shù)只能從 主解釋器的主線程 被調(diào)用;嘗試從另一線程調(diào)用它將導(dǎo)致
ValueError
異常被引發(fā)。handler 將附帶兩個(gè)參數(shù)調(diào)用:信號(hào)編號(hào)和當(dāng)前堆棧幀 (
None
或一個(gè)幀對(duì)象;有關(guān)幀對(duì)象的描述請(qǐng)參閱 類型層級(jí)結(jié)構(gòu)描述 或者參閱inspect
模塊中的屬性描述)。在 Windows 上,
signal()
調(diào)用只能附帶SIGABRT
,SIGFPE
,SIGILL
,SIGINT
,SIGSEGV
,SIGTERM
或SIGBREAK
。 任何其他值都將引發(fā)ValueError
。 請(qǐng)注意不是所有系統(tǒng)都定義了同樣的信號(hào)名稱集合;如果一個(gè)信號(hào)名稱未被定義為SIG*
模塊層級(jí)常量則將引發(fā)AttributeError
。
- signal.sigpending()?
檢查正在等待傳送給調(diào)用方線程的信號(hào)集合(即在阻塞期間被引發(fā)的信號(hào))。 返回正在等待的信號(hào)集合。
可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 sigpending(2)。
另請(qǐng)參閱
pause()
,pthread_sigmask()
和sigwait()
。3.3 新版功能.
- signal.sigwait(sigset)?
掛起調(diào)用方線程的執(zhí)行直到信號(hào)集合 sigset 中指定的信號(hào)之一被傳送。 此函數(shù)會(huì)接受該信號(hào)(將其從等待信號(hào)列表中移除),并返回信號(hào)編號(hào)。
可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 sigwait(3)。
另請(qǐng)參閱
pause()
,pthread_sigmask()
,sigpending()
,sigwaitinfo()
和sigtimedwait()
。3.3 新版功能.
- signal.sigwaitinfo(sigset)?
掛起調(diào)用方線程的執(zhí)行直到信號(hào)集合 sigset 中指定的信號(hào)之一被傳送。 此函數(shù)會(huì)接受該信號(hào)并將其從等待信號(hào)列表中移除。 如果 sigset 中的信號(hào)之一已經(jīng)在等待調(diào)用方線程,此函數(shù)將立即返回并附帶有關(guān)該信號(hào)的信息。 被傳送信號(hào)的信號(hào)處理程序不會(huì)被調(diào)用。 如果該函數(shù)被某個(gè)不在 sigset 中的信號(hào)中斷則會(huì)引發(fā)
InterruptedError
。返回值是一個(gè)代表
siginfo_t
結(jié)構(gòu)體所包含數(shù)據(jù)的對(duì)象,具體為:si_signo
,si_code
,si_errno
,si_pid
,si_uid
,si_status
,si_band
。可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 sigwaitinfo(2)。
另請(qǐng)參閱
pause()
,sigwait()
和sigtimedwait()
。3.3 新版功能.
在 3.5 版更改: 當(dāng)被某個(gè) 不在 sigset 中的信號(hào)中斷時(shí)本函數(shù)將進(jìn)行重試并且信號(hào)處理程序不會(huì)引發(fā)異常(請(qǐng)參閱 PEP 475 了解其理由)。
- signal.sigtimedwait(sigset, timeout)?
類似于
sigwaitinfo()
,但會(huì)接受一個(gè)額外的 timeout 參數(shù)來指定超時(shí)限制。 如果將 timeout 指定為0
,則會(huì)執(zhí)行輪詢。 如果發(fā)生超時(shí)則返回None
。可用性: Unix。 更多信息請(qǐng)參見手冊(cè)頁面 sigtimedwait(2)。
另請(qǐng)參閱
pause()
,sigwait()
和sigwaitinfo()
。3.3 新版功能.
在 3.5 版更改: 現(xiàn)在當(dāng)此函數(shù)被某個(gè)不在 sigset 中的信號(hào)中斷時(shí)將以計(jì)算出的 timeout 進(jìn)行重試并且信號(hào)處理程序不會(huì)引發(fā)異常(請(qǐng)參閱 PEP 475 了解其理由)。
Examples?
這是一個(gè)最小示例程序。 它使用 alarm()
函數(shù)來限制等待打開一個(gè)文件所花費(fèi)的時(shí)間;這在文件為無法開啟的串行設(shè)備時(shí)會(huì)很有用處,此情況通常會(huì)導(dǎo)致 os.open()
無限期地掛起。 解決辦法是在打開文件之前設(shè)置 5 秒鐘的 alarm;如果操作耗時(shí)過長(zhǎng),將會(huì)發(fā)送 alarm 信號(hào),并且處理程序會(huì)引發(fā)一個(gè)異常。
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
對(duì)于 SIGPIPE 的說明?
將你的程序用管道輸出到工具例如 head(1) 將會(huì)導(dǎo)致 SIGPIPE
信號(hào)在其標(biāo)準(zhǔn)輸出的接收方提前關(guān)閉時(shí)被發(fā)送到你的進(jìn)程。 這將引發(fā)一個(gè)異常例如 BrokenPipeError: [Errno 32] Broken pipe
。 要處理這種情況,請(qǐng)對(duì)你的入口點(diǎn)進(jìn)行包裝以捕獲此異常,如下所示:
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 occcurs 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...")