<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從 Go 語言一個文件描述符錯誤講起

          共 6317字,需瀏覽 13分鐘

           ·

          2021-02-28 20:36

          先來看一個 demo:

               1 package main
               2 
               3 import (
               4  "fmt"
               5  "net"
               6  "os"
               7  "runtime"
               8 )
               9 
              10 var rawFileList []*os.File
              11 
              12 func main() {
              13  l, err := net.Listen("tcp"":12345")
              14  if err != nil {
              15   fmt.Println(err)
              16   return
              17  }
              18 
              19  var connList []net.Conn
              20  for {
              21   conn, err := l.Accept()
              22   connList = append(connList, conn)
              23   if err != nil {
              24    fmt.Println(err)
              25    return
              26   }
              27 
              28   go func() {
              29    f, err := conn.(*net.TCPConn).File()
              30    if err != nil {
              31     fmt.Println(err)
              32     return
              33    }
              34 
              35    rawFile := os.NewFile(f.Fd(), "")
              36    rawFileList = append(rawFileList, rawFile)
              37    _ = rawFile
              38    for {
              39     var buf = make([]byte, 1024)
              40     conn.Read(buf)
              41     conn.Write([]byte(`HTTP/1.1 200 OK
              42 Connection: Keep-Alive
              43 Content-Length: 0
              44 Content-Type: text/html
              45 Server: Apache
              46 
              47 `))
              48     runtime.GC()
              49    }
              50   }()
              51  }
              52 }

          可以認為是一個簡單 read request,write response 的 http server,用 wrk 壓的話,也能正常運行:

          ~ ??? wrk http://localhost:12345
          Running 10s test @ http://localhost:12345
            2 threads and 10 connections
            Thread Stats   Avg      Stdev     Max   +/- Stdev
              Latency   589.84us    0.86ms  27.30ms   98.98%
              Req/Sec     9.19k     1.00k   10.91k    68.50%
            183093 requests in 10.02s, 16.94MB read
          Requests/sec:  18278.93
          Transfer/sec:      1.69MB

          進程也沒有什么錯誤日志,把上面的代碼注釋掉第 36 行再用 wrk 壓測,這回結果就不一樣了:

          file tcp [::1]:12345->[::1]:58949: fcntl: bad file descriptor

          這個結果還是有點令人意外的,我們又沒有主動關閉連接,為什么會出現(xiàn) bad file descriptor?

          在代碼中,我們使用連接的 rawFile 的 fd 新建了一個文件:

              29    f, err := conn.(*net.TCPConn).File()
              30    if err != nil {
              31     fmt.Println(err)
              32     return
              33    }
              34 
              35    rawFile := os.NewFile(f.Fd(), "") // 這里
              36    rawFileList = append(rawFileList, rawFile)
              37    _ = rawFile

          注釋掉 36 和沒注釋有什么區(qū)別呢?是誰把我們的連接給關了?

          答案比較簡單,rawFileList 是在堆上分配的全局對象,我們把 rawFile 追加進該數(shù)組后,GC 時便不會回收 rawFile。在 Go 語言中,文件類型在 GC 回收時會執(zhí)行其 close 動作,這是通過 newFile 時的 SetFinalizer 完成的:

          func newFile(fd uintptr, name string, kind newFileKind) *File {

              ... 省略
           runtime.SetFinalizer(f.file, (*file).close)
           
           return f
          }

          也就是說所有文件類型都會在 GC 時被 close,在本文開頭的 demo 中,這個被 close 的文件是我們用 raw fd 創(chuàng)建出來的,而 raw fd 本身是 uintptr 類型。我們知道,帶 GC 的語言,對象之間主要是通過指針引用的,當我們用 uintptr 來創(chuàng)建新文件時,其實已經(jīng)把這個引用關系破壞掉了:


          右邊的 NewFile 如果被 GC 先回收了,那么左邊還在用這個文件就會報 bad file descriptor:

          這時候可能有讀者會覺得奇怪了,按說 net.Conn 是有 File 方法的,為什么我們直接用 File 這個方法生成出來的文件就沒有問題?

          那是因為 File 的實現(xiàn)中,將原有的 fd 復制了一份:

          func (c *conn) File() (f *os.File, err error) {
           f, err = c.fd.dup() // 復制 fd
           if err != nil {
            err = &OpError{Op: "file", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
           }
           return
          }

          dup 操作會在 fd 上增加一個引用計數(shù),當引用計數(shù)減為 0 時,才會執(zhí)行 finalizer。

          綜上,看起來是個很簡單的問題,生產環(huán)境查起來還是要費一些時間。因為類似的問題并不常見,祝你好運。

          歡迎大家關注Xargin的公眾號:



          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學習資料禮包,包含學習建議:入門看什么,進階看什么。關注公眾號 「polarisxu」,回復 ebook 獲??;還可以回復「進群」,和數(shù)萬 Gopher 交流學習。

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天操天天射天天日 | 亚洲香焦巨胸女人网视频 | 国产黄色片在线播放 | 靠逼网站视频 | 亚洲成人无码电影 |