對於linux go1.5版本的一種tcp監聽關閉處理方式
tchannel-go專案作者prashantv對golang1.5 linux版本socket Accept方法的封裝
問題描述
作者在linux上測試tchannel-go專案時發現,當關閉服務端的listener後,有時候還是有一些client connection能夠進來。後來作者對當時的go1.5版本進行大量測試,確實發現存在,當服務端主動關閉後,client還能夠進來。但是在osx其他平臺不會發現這個問題。
當listener做關閉操作後,然後client再發起請求建立連線。實際上它只是標記socket為Closed狀態,但是不會影響epoll接收新連線。
詳細解釋:
如果epoll所在的監聽佇列上有新來的連線,這時socket accept正在從該佇列上獲取新來的連線。這時,如果server主動關閉listener,則因為server端存在連線引用,所以暫時不會關閉,需要等待accept當前新來的連線處理完成後,再關閉並destory fd。
所以出現該問題的主要原因是,當accept正在獲取新來的連線時,因為引用計數不為0,所以導致監聽無法真正關閉。只有當前accept獲取到新來的連線後,才會使得引用計數降為0,則時才會真正關閉監聽。
問題解決
所以針對這個go1.5版本linux等存在的缺陷,需要在外層引入引用計數和條件變數,當accept正在阻塞或獲取新來的連線時,如果server直接關閉監聽,則正在阻塞狀態的goroutine,直接收到server關閉error;如果正在獲取新來的連線,則外層加一個引用計數,當獲取完成後,在減去這個引用計數;在server的close方法包裝一層,如果這個引用計數不為0,則阻塞當前這個goroutine,直到引用計數等於0,再退出。這樣保證了accept不會再接收到新的連線。
程式碼示例
// 對net.Listener的封裝,引入引用計數和條件變數
type SaneListener struct {
l net.Listener
c *sync.Cond
refCount int
}
// 當進入Accept之前,引用計數做加一操作,防止server主動Close listener操作時,因為底層的引用計數不為0,導致暫時不會發生真正的close fd操作。
// SaleListener的Close操作,因為refCount引用計數不為0,則Close暫時不會退出。底層的監聽已關閉,但是會等待accept獲取新連線操作處理完成,這樣close操作就相當於滯後了一個連線處理。
func (s *SaneListener) incRef() {
s.c.L.Lock()
s.refCount++
s.c.L.Unlock()
}
// 當accept獲取到新來的連線或者獲取到一個server監聽關閉error,引用計數減一
// 這樣SaneListener的Close操作,因為引用計數關閉,則真正關閉。
//
// 由於SaneListener的Close操作,如果server正在獲取新連線,則該goroutine會發生條件阻塞;等待accept操作完成後,通過Broadcast操作喚醒睡眠的goroutine,繼續Close操作。
func (s *SaneListener) decRef() {
s.c.L.Lock()
s.refCount---
s.c.Broadcast()
s.c.L.Unlock()
}
// accept操作
func (s *SaneListener) Accept() (net.Conn, error){
s.incRef()
defer s.decRef()
return s.l.Accept()
}
// Close操作:底層的監聽是提前關閉了,但是epoll佇列中正在被accept的新連線還尚在處理中,所以底層的引用計數不等於0,則需要該操作完成後,再退出Close呼叫。這樣,在Close操作後,server不會接收新的連線了
func (s *SaneListener) Close() error {
err := s.l.Close()
if err == nil {
s.c.L.Lock()
for s.refCount > 0 {
s.c.Wait()
}
s.c.L.Unlock()
}
return err
}
func (s *SaneListener) Addr() net.Addr {
return s.l.Addr()
}
條件變數cond,使得refCount大於0時,主動阻塞該goroutine;等待accept完成獲取新連線或者獲取到error操作後,在通過decRef的Broadcast廣播喚醒阻塞的goutines。
小結
我們可以看到server對監聽關閉操作,當accept正在獲取新來的連線時,因引用計數不為0,則不會真正的destroy掉net fd。通過引入上層引用計數,來達到當關閉監聽後,確保server不會再接收新連線了。這個引用計數和條件變數大家可以認真學習,同時學習下golang的網路庫。
參考資料
net: Listener sometimes accepts connections after Close
後記
// go1.10.2/src/internal/poll/fd_unix.go 第93行
// 我們可以看到當底層accept獲取新連線的引用計數為0時,才會真正destory掉net fd。
77 // Close closes the FD. The underlying file descriptor is closed by the
78 // destroy method when there are no remaining references.
79 func (fd *FD) Close() error {
80 if !fd.fdmu.increfAndClose() {
81 return errClosing(fd.isFile)
82 }
83
84 // Unblock any I/O. Once it all unblocks and returns,
85 // so that it cannot be referring to fd.sysfd anymore,
86 // the final decref will close fd.sysfd. This should happen
87 // fairly quickly, since all the I/O is non-blocking, and any
88 // attempts to block in the pollDesc will return errClosing(fd.isFile).
89 fd.pd.evict()
90
91 // The call to decref will call destroy if there are no other
92 // references.
93 err := fd.decref()
94
95 // Wait until the descriptor is closed. If this was the only
96 // reference, it is already closed. Only wait if the file has
97 // not been set to blocking mode, as otherwise any current I/O
98 // may be blocking, and that would block the Close.
99 if !fd.isBlocking {
100 runtime_Semacquire(&fd.csema)
101 }
102
103 return err
104 }
// go1.10.2/src/internal/poll/fd_mutex.go
209 func (fd *FD) decref() error {
210 if fd.fdmu.decref() {
211 return fd.destroy()
212 }
213 return nil
214 }
相關文章
- Android的事件處理——監聽介面方式Android事件
- 關於Android的幾種事件處理Android事件
- LoadRunner中多值關聯的3種處理方式
- Oracle 監聽異常處理Oracle
- 關於linux病毒`kinsing` `kdevtmpfsi`的處理Linuxdev
- 關於attention中對padding的處理:maskpadding
- 多對一處理 和一對多處理的處理
- 在duplicate時,出現監聽BLOCKED的情況,導致監聽自動關閉BloC
- 關於EditText焦點監聽
- 關於叢集節點timeline不一致的處理方式
- 針對於早期版本的flutter開發的app的處理FlutterAPP
- 關於 Eloquent ORM 對資料處理的思考ORM
- PostgreSQL-三種關閉方式(二)SQL
- PHP處理密碼的幾種方式PHP密碼
- SQLite 併發的四種處理方式SQLite
- php 與 nginx 的兩種處理方式PHPNginx
- SpringMVC非同步處理的 5 種方式SpringMVC非同步
- linux中cp複製時處理軟連結的兩種方式Linux
- ASP.NET Core中配置監聽URLs的五種方式ASP.NET
- Netty事件監聽和處理(上)【有福利】Netty事件
- Netty事件監聽和處理(下)【有福利】Netty事件
- 關於一類資料處理
- 小白:關於處理“can't find '__main__' module in ”這個問題的詳細處理方式!AI
- 關於OT分類的一些處理
- 關於丟失表空間資料檔案的處理方式
- Laravel 路由版本實現的一種方式Laravel路由
- 【js】版本號對比處理方案JS
- 如何監聽頁面關閉或重新整理動作
- HBase協處理器載入的三種方式
- sql server對於日期的處理SQLServer
- Netty中自定義事件處理程式和監聽器Netty事件
- 使用java程式,監聽tcp協議埠JavaTCP協議
- 關於Python中的日期處理Python
- android版本與linux核心版本對應關係AndroidLinux
- 你知道前端對圖片的處理方式嗎?前端
- 幾種常見的延遲執行處理方式
- [20210722]資料庫異常關閉的處理.txt資料庫
- 我為 Netty 貢獻原始碼 | 且看 Netty 如何應對 TCP 連線的正常關閉,異常關閉,半關閉場景Netty原始碼TCP