signal --- 設置異步事件處理程序?


該模塊提供了在 Python 中使用信號處理程序的機制。

一般規則?

signal.signal() 函數允許定義在接收到信號時(shí)執行的自定義處理程序。少量的默認處理程序已經(jīng)設置: SIGPIPE 被忽略(因此管道和套接字上的寫(xiě)入錯誤可以報告為普通的 Python 異常)以及如果父進(jìn)程沒(méi)有更改 SIGINT ,則其會(huì )被翻譯成 KeyboardInterrupt 異常。

一旦設置,特定信號的處理程序將保持安裝,直到它被顯式重置( Python 模擬 BSD 樣式接口而不管底層實(shí)現),但 SIGCHLD 的處理程序除外,它遵循底層實(shí)現。

執行 Python 信號處理程序?

Python 信號處理程序不會(huì )在低級( C )信號處理程序中執行。相反,低級信號處理程序設置一個(gè)標志,告訴 virtual machine 稍后執行相應的 Python 信號處理程序(例如在下一個(gè) bytecode 指令)。這會(huì )導致:

  • 捕獲同步錯誤是沒(méi)有意義的,例如 SIGFPESIGSEGV ,它們是由 C 代碼中的無(wú)效操作引起的。Python 將從信號處理程序返回到 C 代碼,這可能會(huì )再次引發(fā)相同的信號,導致 Python 顯然的掛起。 從Python 3.3開(kāi)始,你可以使用 faulthandler 模塊來(lái)報告同步錯誤。

  • 純 C 中實(shí)現的長(cháng)時(shí)間運行的計算(例如在大量文本上的正則表達式匹配)可以在任意時(shí)間內不間斷地運行,而不管接收到任何信號。計算完成后將調用 Python 信號處理程序。

  • 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.

信號與線(xiàn)程?

Python 信號處理程序總是會(huì )在主 Python 主解釋器的主線(xiàn)程中執行,即使信號是在另一個(gè)線(xiàn)程中接收的。 這意味著(zhù)信號不能被用作線(xiàn)程間通信的手段。 你可以改用 threading 模塊中的同步原語(yǔ)。

此外,只有主解釋器的主線(xiàn)程才被允許設置新的信號處理程序。

模塊內容?

在 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.

Availability: Unix. See the man page sigprocmask(3) and pthread_sigmask(3) for further information.

3.5 新版功能.

signal 模塊中定義的變量是:

signal.SIG_DFL?

這是兩種標準信號處理選項之一;它只會(huì )執行信號的默認函數。 例如,在大多數系統上,對于 SIGQUIT 的默認操作是轉儲核心并退出,而對于 SIGCHLD 的默認操作是簡(jiǎn)單地忽略它。

signal.SIG_IGN?

這是另一個(gè)標準信號處理程序,它將簡(jiǎn)單地忽略給定的信號。

signal.SIGABRT?

來(lái)自 abort(3) 的中止信號。

signal.SIGALRM?

來(lái)自 alarm(2) 的計時(shí)器信號。

可用性: Unix。

signal.SIGBREAK?

來(lái)自鍵盤(pán)的中斷 (CTRL + BREAK)。

可用性: Windows。

signal.SIGBUS?

總線(xiàn)錯誤 (非法的內存訪(fǎng)問(wèn))。

可用性: Unix。

signal.SIGCHLD?

子進(jìn)程被停止或終結。

可用性: Unix。

signal.SIGCLD?

SIGCHLD 的別名。

signal.SIGCONT?

如果進(jìn)程當前已停止則繼續執行它

可用性: Unix。

signal.SIGFPE?

浮點(diǎn)異常。 例如除以零。

參見(jiàn)

當除法或求余運算的第二個(gè)參數為零時(shí)會(huì )引發(fā) ZeroDivisionError 。

signal.SIGHUP?

在控制終端上檢測到掛起或控制進(jìn)程的終止。

可用性: Unix。

signal.SIGILL?

非法指令。

signal.SIGINT?

來(lái)自鍵盤(pán)的中斷 (CTRL + C)。

默認的動(dòng)作是引發(fā) KeyboardInterrupt。

signal.SIGKILL?

終止信號。

它不能被捕獲、阻塞或忽略。

可用性: Unix。

signal.SIGPIPE?

損壞的管道:寫(xiě)入到?jīng)]有讀取器的管道。

默認的動(dòng)作是忽略此信號。

可用性: Unix。

signal.SIGSEGV?

段錯誤:無(wú)效的內存引用。

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?

用戶(hù)自定義信號 1。

可用性: Unix。

signal.SIGUSR2?

用戶(hù)自定義信號 2。

可用性: Unix。

signal.SIGWINCH?

窗口調整大小信號。

可用性: Unix。

SIG*

所有信號編號都是符號化定義的。 例如,掛起信號被定義為 signal.SIGHUP;變量的名稱(chēng)與 C 程序中使用的名稱(chēng)相同,具體見(jiàn) <signal.h>。 'signal()' 的 Unix 手冊頁(yè)面列出了現有的信號 (在某些系統上這是 signal(2),在其他系統中此列表則是在 signal(7) 中)。 請注意并非所有系統都會(huì )定義相同的信號名稱(chēng)集;只有系統所定義的名稱(chēng)才會(huì )由此模塊來(lái)定義。

signal.CTRL_C_EVENT?

對應于 Ctrl+C 擊鍵事件的信號。此信號只能用于 os.kill() 。

可用性: Windows。

3.2 新版功能.

signal.CTRL_BREAK_EVENT?

對應于 Ctrl+Break 擊鍵事件的信號。此信號只能用于 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_REAL?

實(shí)時(shí)遞減間隔計時(shí)器,并在到期時(shí)發(fā)送 SIGALRM 。

signal.ITIMER_VIRTUAL?

僅在進(jìn)程執行時(shí)遞減間隔計時(shí)器,并在到期時(shí)發(fā)送 SIGVTALRM 。

signal.ITIMER_PROF?

當進(jìn)程執行時(shí)以及當系統替進(jìn)程執行時(shí)都會(huì )減小間隔計時(shí)器。 這個(gè)計時(shí)器與 ITIMER_VIRTUAL 相配結,通常被用于分析應用程序在用戶(hù)和內核空間中花費的時(shí)間。 SIGPROF 會(huì )在超期時(shí)被發(fā)送。

signal.SIG_BLOCK?

pthread_sigmask()how 形參的一個(gè)可能的值,表明信號將會(huì )被阻塞。

3.3 新版功能.

signal.SIG_UNBLOCK?

pthread_sigmask()how 形參的是個(gè)可能的值,表明信號將被解除阻塞。

3.3 新版功能.

signal.SIG_SETMASK?

pthread_sigmask()how 形參的一個(gè)可能的值,表明信號掩碼將要被替換。

3.3 新版功能.

signal 模塊定義了一個(gè)異常:

exception signal.ItimerError?

作為來(lái)自下層 setitimer()getitimer() 實(shí)現錯誤的信號被引發(fā)。 如果將無(wú)效的定時(shí)器或負的時(shí)間值傳給 setitimer() 就導致這個(gè)錯誤。 此錯誤是 OSError 的子類(lèi)型。

3.3 新版功能: 此錯誤是 IOError 的子類(lèi)型,現在則是 OSError 的別名。

signal 模塊定義了以下函數:

signal.alarm(time)?

如果 time 值非零,則此函數將要求將一個(gè) SIGALRM 信號在 time 秒內發(fā)往進(jìn)程。 任何在之前排入計劃的警報都會(huì )被取消(在任何時(shí)刻都只能有一個(gè)警報被排入計劃)。 后續的返回值將是任何之前設置的警報被傳入之前的秒數。 如果 time 值為零,則不會(huì )將任何警報排入計劃,并且任何已排入計劃的警報都會(huì )被取消。 如果返回值為零,則目前沒(méi)有任何警報被排入計劃。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 alarm(2)。

signal.getsignal(signalnum)?

返回當前用于信號 signalnum 的信號處理程序。 返回值可以是一個(gè) Python 可調用對象,或是特殊值 signal.SIG_IGN, signal.SIG_DFLNone 之一。 在這里,signal.SIG_IGN 表示信號在之前被忽略,signal.SIG_DFL 表示之前在使用默認的信號處理方式,而 None 表示之前的信號處理程序未由 Python 安裝。

signal.strsignal(signalnum)?

返回信號 signalnum 的系統描述,例如 "Interrupt", "Segmentation fault" 等等。 如果信號無(wú)法被識別則返回 None。

3.8 新版功能.

signal.valid_signals()?

返回本平臺上的有效信號編號集。 這可能會(huì )少于 range(1, NSIG),如果某些信號被系統保留作為內部使用的話(huà)。

3.8 新版功能.

signal.pause()?

使進(jìn)程休眠直至接收到一個(gè)信號;然后將會(huì )調用適當的處理程序。 返回空值。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 signal(2)。

另請參閱 sigwait(), sigwaitinfo(), sigtimedwait()sigpending()。

signal.raise_signal(signum)?

向調用方進(jìn)程發(fā)送一個(gè)信號。 返回空值。

3.8 新版功能.

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

發(fā)送信號 sig 到文件描述符 pidfd 所指向的進(jìn)程。 Python 目前不支持 siginfo 形參;它必須為 None。 提供 flags 參數是為了將來(lái)擴展;當前未定義旗標值。

更多信息請參閱 pidfd_send_signal(2) 手冊頁(yè)面。

可用性: Linux 5.1+

3.9 新版功能.

signal.pthread_kill(thread_id, signalnum)?

將信號 signalnum 發(fā)送至與調用者在同一進(jìn)程中另一線(xiàn)程 thread_id。 目標線(xiàn)程可被用于執行任何代碼(Python或其它)。 但是,如果目標線(xiàn)程是在執行 Python 解釋器,則 Python 信號處理程序將 由主解釋器的主線(xiàn)程來(lái)執行。 因此,將信號發(fā)送給特定 Python 線(xiàn)程的唯一作用在于強制讓一個(gè)正在運行的系統調用失敗并拋出 InterruptedError。

使用 threading.get_ident()threading.Thread 對象的 ident 屬性為 thread_id 獲取合適的值。

如果 signalnum 為 0,則不會(huì )發(fā)送信號,但仍然會(huì )執行錯誤檢測;這可被用來(lái)檢測目標線(xiàn)程是否仍在運行。

引發(fā)一個(gè) 審計事件 signal.pthread_kill,附帶參數 thread_id, signalnum。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 pthread_kill(3)。

另請參閱 os.kill()。

3.3 新版功能.

signal.pthread_sigmask(how, mask)?

獲取和/或修改調用方線(xiàn)程的信號掩碼。 信號掩碼是一組傳送過(guò)程目前為調用者而阻塞的信號集。 返回舊的信號掩碼作為一組信號。

該調用的行為取決于 how 的值,具體見(jiàn)下。

  • SIG_BLOCK: 被阻塞信號集是當前集與 mask 參數的并集。

  • SIG_UNBLOCK: mask 中的信號會(huì )從當前已阻塞信號集中被移除。 允許嘗試取消對一個(gè)非阻塞信號的阻塞。

  • SIG_SETMASK: 已阻塞信號集會(huì )被設為 mask 參數的值。

mask 是一個(gè)信號編號集合 (例如 {signal.SIGINT, signal.SIGTERM})。 請使用 valid_signals() 表示包含所有信號的完全掩碼。

例如,signal.pthread_sigmask(signal.SIG_BLOCK, []) 會(huì )讀取調用方線(xiàn)程的信號掩碼。

SIGKILLSIGSTOP 不能被阻塞。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 sigprocmask(2)pthread_sigmask(3)。

另請參閱 pause(), sigpending()sigwait()。

3.3 新版功能.

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

設置由 which 指明的給定間隔計時(shí)器 (signal.ITIMER_REAL, signal.ITIMER_VIRTUALsignal.ITIMER_PROF 之一) 在 seconds 秒 (接受浮點(diǎn)數值,為與 alarm() 之差) 之后開(kāi)始并在每 interval 秒間隔時(shí) (如果 interval 不為零) 啟動(dòng)。 由 which 指明的間隔計時(shí)器可通過(guò)將 seconds 設為零來(lái)清空。

當一個(gè)間隔計時(shí)器啟動(dòng)時(shí),會(huì )有信號發(fā)送至進(jìn)程。 所發(fā)送的具體信號取決于所使用的計時(shí)器;signal.ITIMER_REAL 將發(fā)送 SIGALRM, signal.ITIMER_VIRTUAL 將發(fā)送 SIGVTALRM, 而 signal.ITIMER_PROF 將發(fā)送 SIGPROF.

原有的值會(huì )以元組: (delay, interval) 的形式被返回。

嘗試傳入無(wú)效的計時(shí)器將導致 ItimerError。

可用性: Unix。

signal.getitimer(which)?

返回由 which 指明的給定間隔計時(shí)器當前的值。

可用性: Unix。

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

將喚醒文件描述符設為 fd。 當接收到信號時(shí),會(huì )將信號編號以單個(gè)字節的形式寫(xiě)入 fd。 這可被其它庫用來(lái)喚醒一次 poll 或 select 調用,以允許該信號被完全地處理。

原有的喚醒 fd 會(huì )被返回(或者如果未啟用文件描述符喚醒則返回 -1)。 如果 fd 為 -1,文件描述符喚醒會(huì )被禁用。 如果不為 -1,則 fd 必須為非阻塞型。 需要由庫來(lái)負責在重新調用 poll 或 select 之前從 fd 移除任何字節數據。

當啟用線(xiàn)程用時(shí),此函數只能從 主解釋器的主線(xiàn)程 被調用;嘗試從另一線(xiàn)程調用它將導致 ValueError 異常被引發(fā)。

使用此函數有兩種通常的方式。 在兩種方式下,當有信號到達時(shí)你都是用 fd 來(lái)喚醒,但之后它們在確定達到的一個(gè)或多個(gè)信號 which 時(shí)存在差異。

在第一種方式下,我們從 fd 的緩沖區讀取數據,這些字節值會(huì )給你信號編號。 這種方式很簡(jiǎn)單,但在少數情況下會(huì )發(fā)生問(wèn)題:通常 fd 將有緩沖區空間大小限制,如果信號到達得太多且太快,緩沖區可能會(huì )爆滿(mǎn),有些信號可能丟失。 如果你使用此方式,則你應當設置 warn_on_full_buffer=True,當信號丟失時(shí)這至少能將警告消息打印到 stderr。

在第二種方式下,我們 只會(huì ) 將喚醒 fd 用于喚醒,而忽略實(shí)際的字節值。 在此情況下,我們所關(guān)心的只有 fd 的緩沖區為空還是不為空;爆滿(mǎn)的緩沖區完全不會(huì )導致問(wèn)題。 如果你使用此方式,則你應當設置 warn_on_full_buffer=False,這樣你的用戶(hù)就不會(huì )被虛假的警告消息所迷惑。

在 3.5 版更改: 在 Windows 上,此函數現在也支持套接字處理。

在 3.7 版更改: 添加了 warn_on_full_buffer 形參。

signal.siginterrupt(signalnum, flag)?

更改系統調用重啟行為:如果 flagFalse,系統調用將在被信號 signalnum 中斷時(shí)重啟,否則系統調用將被中斷。 返回空值。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 siginterrupt(3)。

請注意用 signal() 安裝信號處理程序將重啟行為重置為可通過(guò)顯式調用 siginterrupt() 并為給定信號的 flag 設置真值來(lái)實(shí)現中斷。

signal.signal(signalnum, handler)?

將信號 signalnum 的處理程序設為函數 handler。 handler 可以為接受兩個(gè)參數(見(jiàn)下)的 Python 可調用對象,或者為特殊值 signal.SIG_IGNsignal.SIG_DFL 之一。 之前的信號處理程序將被返回(參見(jiàn)上文 getsignal() 的描述)。 (更多信息請參閱 Unix 手冊頁(yè)面 signal(2)。)

當啟用線(xiàn)程用時(shí),此函數只能從 主解釋器的主線(xiàn)程 被調用;嘗試從另一線(xiàn)程調用它將導致 ValueError 異常被引發(fā)。

handler 將附帶兩個(gè)參數調用:信號編號和當前堆棧幀 (None 或一個(gè)幀對象;有關(guān)幀對象的描述請參閱 類(lèi)型層級結構描述 或者參閱 inspect 模塊中的屬性描述)。

在 Windows 上,signal() 調用只能附帶 SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERMSIGBREAK。 任何其他值都將引發(fā) ValueError。 請注意不是所有系統都定義了同樣的信號名稱(chēng)集合;如果一個(gè)信號名稱(chēng)未被定義為 SIG* 模塊層級常量則將引發(fā) AttributeError。

signal.sigpending()?

檢查正在等待傳送給調用方線(xiàn)程的信號集合(即在阻塞期間被引發(fā)的信號)。 返回正在等待的信號集合。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 sigpending(2)。

另請參閱 pause(), pthread_sigmask()sigwait()。

3.3 新版功能.

signal.sigwait(sigset)?

掛起調用方線(xiàn)程的執行直到信號集合 sigset 中指定的信號之一被傳送。 此函數會(huì )接受該信號(將其從等待信號列表中移除),并返回信號編號。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 sigwait(3)。

另請參閱 pause(), pthread_sigmask(), sigpending(), sigwaitinfo()sigtimedwait()。

3.3 新版功能.

signal.sigwaitinfo(sigset)?

掛起調用方線(xiàn)程的執行直到信號集合 sigset 中指定的信號之一被傳送。 此函數會(huì )接受該信號并將其從等待信號列表中移除。 如果 sigset 中的信號之一已經(jīng)在等待調用方線(xiàn)程,此函數將立即返回并附帶有關(guān)該信號的信息。 被傳送信號的信號處理程序不會(huì )被調用。 如果該函數被某個(gè)不在 sigset 中的信號中斷則會(huì )引發(fā) InterruptedError。

返回值是一個(gè)代表 siginfo_t 結構體所包含數據的對象,具體為: si_signo, si_code, si_errno, si_pid, si_uid, si_status, si_band。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 sigwaitinfo(2)。

另請參閱 pause(), sigwait()sigtimedwait()。

3.3 新版功能.

在 3.5 版更改: 當被某個(gè) 不在 sigset 中的信號中斷時(shí)本函數將進(jìn)行重試并且信號處理程序不會(huì )引發(fā)異常(請參閱 PEP 475 了解其理由)。

signal.sigtimedwait(sigset, timeout)?

類(lèi)似于 sigwaitinfo(),但會(huì )接受一個(gè)額外的 timeout 參數來(lái)指定超時(shí)限制。 如果將 timeout 指定為 0,則會(huì )執行輪詢(xún)。 如果發(fā)生超時(shí)則返回 None。

可用性: Unix。 更多信息請參見(jiàn)手冊頁(yè)面 sigtimedwait(2)。

另請參閱 pause(), sigwait()sigwaitinfo()。

3.3 新版功能.

在 3.5 版更改: 現在當此函數被某個(gè)不在 sigset 中的信號中斷時(shí)將以計算出的 timeout 進(jìn)行重試并且信號處理程序不會(huì )引發(fā)異常(請參閱 PEP 475 了解其理由)。

Examples?

這是一個(gè)最小示例程序。 它使用 alarm() 函數來(lái)限制等待打開(kāi)一個(gè)文件所花費的時(shí)間;這在文件為無(wú)法開(kāi)啟的串行設備時(shí)會(huì )很有用處,此情況通常會(huì )導致 os.open() 無(wú)限期地掛起。 解決辦法是在打開(kāi)文件之前設置 5 秒鐘的 alarm;如果操作耗時(shí)過(guò)長(cháng),將會(huì )發(fā)送 alarm 信號,并且處理程序會(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

對于 SIGPIPE 的說(shuō)明?

將你的程序用管道輸出到工具例如 head(1) 將會(huì )導致 SIGPIPE 信號在其標準輸出的接收方提前關(guān)閉時(shí)被發(fā)送到你的進(jìn)程。 這將引發(fā)一個(gè)異常例如 BrokenPipeError: [Errno 32] Broken pipe。 要處理這種情況,請對你的入口點(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...")