signal --- 設(shè)置異步事件處理程序?


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

一般規(guī)則?

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

一旦設(shè)置,特定信號的處理程序?qū)⒈3职惭b,直到它被顯式重置( Python 模擬 BSD 樣式接口而不管底層實(shí)現(xiàn)),但 SIGCHLD 的處理程序除外,它遵循底層實(shí)現(xiàn)。

執(zhí)行 Python 信號處理程序?

Python 信號處理程序不會在低級( C )信號處理程序中執(zhí)行。相反,低級信號處理程序設(shè)置一個標(biāo)志,告訴 virtual machine 稍后執(zhí)行相應(yīng)的 Python 信號處理程序(例如在下一個 bytecode 指令)。這會導(dǎo)致:

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

  • 純 C 中實(shí)現(xiàn)的長時間運(yùn)行的計(jì)算(例如在大量文本上的正則表達(dá)式匹配)可以在任意時間內(nèi)不間斷地運(yùn)行,而不管接收到任何信號。計(jì)算完成后將調(diào)用 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.

信號與線程?

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

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

模塊內(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 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?

這是兩種標(biāo)準(zhǔn)信號處理選項(xiàng)之一;它只會執(zhí)行信號的默認(rèn)函數(shù)。 例如,在大多數(shù)系統(tǒng)上,對于 SIGQUIT 的默認(rèn)操作是轉(zhuǎn)儲核心并退出,而對于 SIGCHLD 的默認(rèn)操作是簡單地忽略它。

signal.SIG_IGN?

這是另一個標(biāo)準(zhǔn)信號處理程序,它將簡單地忽略給定的信號。

signal.SIGABRT?

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

signal.SIGALRM?

來自 alarm(2) 的計(jì)時器信號。

可用性: Unix。

signal.SIGBREAK?

來自鍵盤的中斷 (CTRL + BREAK)。

可用性: Windows。

signal.SIGBUS?

總線錯誤 (非法的內(nèi)存訪問)。

可用性: Unix。

signal.SIGCHLD?

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

可用性: Unix。

signal.SIGCLD?

SIGCHLD 的別名。

signal.SIGCONT?

如果進(jìn)程當(dāng)前已停止則繼續(xù)執(zhí)行它

可用性: Unix。

signal.SIGFPE?

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

參見

當(dāng)除法或求余運(yùn)算的第二個參數(shù)為零時會引發(fā) ZeroDivisionError

signal.SIGHUP?

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

可用性: Unix。

signal.SIGILL?

非法指令。

signal.SIGINT?

來自鍵盤的中斷 (CTRL + C)。

默認(rèn)的動作是引發(fā) KeyboardInterrupt。

signal.SIGKILL?

終止信號。

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

可用性: Unix。

signal.SIGPIPE?

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

默認(rèn)的動作是忽略此信號。

可用性: Unix。

signal.SIGSEGV?

段錯誤:無效的內(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é)信號。

signal.SIGUSR1?

用戶自定義信號 1。

可用性: Unix。

signal.SIGUSR2?

用戶自定義信號 2。

可用性: Unix。

signal.SIGWINCH?

窗口調(diào)整大小信號。

可用性: Unix。

SIG*

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

signal.CTRL_C_EVENT?

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

可用性: Windows。

3.2 新版功能.

signal.CTRL_BREAK_EVENT?

對應(yīng)于 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í)時遞減間隔計(jì)時器,并在到期時發(fā)送 SIGALRM 。

signal.ITIMER_VIRTUAL?

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

signal.ITIMER_PROF?

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

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() 實(shí)現(xiàn)錯誤的信號被引發(fā)。 如果將無效的定時器或負(fù)的時間值傳給 setitimer() 就導(dǎo)致這個錯誤。 此錯誤是 OSError 的子類型。

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

signal 模塊定義了以下函數(shù):

signal.alarm(time)?

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

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

signal.getsignal(signalnum)?

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

signal.strsignal(signalnum)?

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

3.8 新版功能.

signal.valid_signals()?

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

3.8 新版功能.

signal.pause()?

使進(jìn)程休眠直至接收到一個信號;然后將會調(diào)用適當(dāng)?shù)奶幚沓绦颉?返回空值。

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

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

signal.raise_signal(signum)?

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

3.8 新版功能.

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

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

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

可用性: Linux 5.1+

3.9 新版功能.

signal.pthread_kill(thread_id, signalnum)?

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

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

如果 signalnum 為 0,則不會發(fā)送信號,但仍然會執(zhí)行錯誤檢測;這可被用來檢測目標(biāo)線程是否仍在運(yùn)行。

引發(fā)一個 審計(jì)事件 signal.pthread_kill,附帶參數(shù) thread_id, signalnum。

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

另請參閱 os.kill()。

3.3 新版功能.

signal.pthread_sigmask(how, mask)?

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

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

  • SIG_BLOCK: 被阻塞信號集是當(dāng)前集與 mask 參數(shù)的并集。

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

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

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

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

SIGKILLSIGSTOP 不能被阻塞。

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

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

3.3 新版功能.

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

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

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

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

嘗試傳入無效的計(jì)時器將導(dǎo)致 ItimerError

可用性: Unix。

signal.getitimer(which)?

返回由 which 指明的給定間隔計(jì)時器當(dāng)前的值。

可用性: Unix。

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

將喚醒文件描述符設(shè)為 fd。 當(dāng)接收到信號時,會將信號編號以單個字節(jié)的形式寫入 fd。 這可被其它庫用來喚醒一次 poll 或 select 調(diào)用,以允許該信號被完全地處理。

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

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

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

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

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

在 3.5 版更改: 在 Windows 上,此函數(shù)現(xiàn)在也支持套接字處理。

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

signal.siginterrupt(signalnum, flag)?

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

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

請注意用 signal() 安裝信號處理程序?qū)⒅貑⑿袨橹刂脼榭赏ㄟ^顯式調(diào)用 siginterrupt() 并為給定信號的 flag 設(shè)置真值來實(shí)現(xiàn)中斷。

signal.signal(signalnum, handler)?

將信號 signalnum 的處理程序設(shè)為函數(shù) handlerhandler 可以為接受兩個參數(shù)(見下)的 Python 可調(diào)用對象,或者為特殊值 signal.SIG_IGNsignal.SIG_DFL 之一。 之前的信號處理程序?qū)⒈环祷兀▍⒁娚衔?getsignal() 的描述)。 (更多信息請參閱 Unix 手冊頁面 signal(2)。)

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

handler 將附帶兩個參數(shù)調(diào)用:信號編號和當(dāng)前堆棧幀 (None 或一個幀對象;有關(guān)幀對象的描述請參閱 類型層級結(jié)構(gòu)描述 或者參閱 inspect 模塊中的屬性描述)。

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

signal.sigpending()?

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

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

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

3.3 新版功能.

signal.sigwait(sigset)?

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

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

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

3.3 新版功能.

signal.sigwaitinfo(sigset)?

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

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

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

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

3.3 新版功能.

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

signal.sigtimedwait(sigset, timeout)?

類似于 sigwaitinfo(),但會接受一個額外的 timeout 參數(shù)來指定超時限制。 如果將 timeout 指定為 0,則會執(zhí)行輪詢。 如果發(fā)生超時則返回 None。

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

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

3.3 新版功能.

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

Examples?

這是一個最小示例程序。 它使用 alarm() 函數(shù)來限制等待打開一個文件所花費(fèi)的時間;這在文件為無法開啟的串行設(shè)備時會很有用處,此情況通常會導(dǎo)致 os.open() 無限期地掛起。 解決辦法是在打開文件之前設(shè)置 5 秒鐘的 alarm;如果操作耗時過長,將會發(fā)送 alarm 信號,并且處理程序會引發(fā)一個異常。

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) 將會導(dǎo)致 SIGPIPE 信號在其標(biāo)準(zhǔn)輸出的接收方提前關(guān)閉時被發(fā)送到你的進(jìn)程。 這將引發(fā)一個異常例如 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...")