先来看一个 demo:

     1 package main2 3 import (4  "fmt"5  "net"6  "os"7  "runtime"8 )9 10 var rawFileList []*os.File11 12 func main() {13  l, err := net.Listen("tcp", ":12345")14  if err != nil {15   fmt.Println(err)16   return17  }18 19  var connList []net.Conn20  for {21   conn, err := l.Accept()22   connList = append(connList, conn)23   if err != nil {24    fmt.Println(err)25    return26   }27 28   go func() {29    f, err := conn.(*net.TCPConn).File()30    if err != nil {31     fmt.Println(err)32     return33    }34 35    rawFile := os.NewFile(f.Fd(), "")36    rawFileList = append(rawFileList, rawFile)37    _ = rawFile38    for {39     var buf = make([]byte, 1024)40     conn.Read(buf)41     conn.Write([]byte(`HTTP/1.1 200 OK42 Connection: Keep-Alive43 Content-Length: 044 Content-Type: text/html45 Server: Apache46 47 `))48     runtime.GC()49    }50   }()51  }52 }

可以认为是一个简单 read request,write response 的 http server,用 wrk 压的话,也能正常运行:

~ ❯❯❯ wrk http://localhost:12345
Running 10s test @ http://localhost:123452 threads and 10 connectionsThread Stats   Avg      Stdev     Max   +/- StdevLatency   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

这个结果还是有点令人意外的,我们又没有主动关闭连接,为什么会出现 bad file descriptor?

在代码中,我们使用连接的 rawFile 的 fd 新建了一个文件:

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

注释掉 36 和没注释有什么区别呢?是谁把我们的连接给关了?

答案比较简单,rawFileList 是在堆上分配的全局对象,我们把 rawFile 追加进该数组后,GC 时便不会回收 rawFile。在 Go 语言中,文件类型在 GC 回收时会执行其 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 创建出来的,而 raw fd 本身是 uintptr 类型。我们知道,带 GC 的语言,对象之间主要是通过指针引用的,当我们用 uintptr 来创建新文件时,其实已经把这个引用关系破坏掉了:


右边的 NewFile 如果被 GC 先回收了,那么左边还在用这个文件就会报 bad file descriptor:

这时候可能有读者会觉得奇怪了,按说 net.Conn 是有 File 方法的,为什么我们直接用 File 这个方法生成出来的文件就没有问题?

那是因为 File 的实现中,将原有的 fd 复制了一份:

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

dup 操作会在 fd 上增加一个引用计数,当引用计数减为 0 时,才会执行 finalizer。

综上,看起来是个很简单的问题,生产环境查起来还是要费一些时间。因为类似的问题并不常见,祝你好运。

一个 bad file descriptor 的问题相关推荐

  1. linux 安装包 在此作用域中尚未声明_Linux运行go项目报错:copy_file_range: bad file descriptor...

    这两天在 Linux 环境部署一个 Go 项目遇到一个报错:copy_file_range: bad file descriptor.网上查找各种方法,花了两天的时间,经过一番折腾后才解决,觉得非常有 ...

  2. php扩展dio,PHP Dio扩展新函数dio_fdopen参数返回--bad file descriptor的分

    昨天准备做一个程序,PHP的串口扩展程序,用来做串口打开的,于是用dio_fdopen来新建一个文件: view plaincopy to clipboardprint? 1. 2.<?php ...

  3. This file can not be opened as a file descriptor; it is probably compressed

    链接:FileNotFoundException: This file can not be opened as a file descriptor; it is probably compresse ...

  4. 文件描述符file descriptor与inode的相关知识

    每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的 信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块 (PCB,Process ...

  5. logstash 报错Bad file descriptor - Bad file descriptor

    使用bin/logstash -f config/D_HINFO_PDT.conf 启动日志中没有错误: 我使用 nohup bin/logstash -f config/D_HINFO_PDT.co ...

  6. i节点(inode)和文件描述符(file descriptor)的区别和联系

    http://blog.csdn.net/wangchaoxjtuse/article/details/6036816 inode 或i节点是指对文件的索引.如一个系统,所有文件是放在磁盘或flash ...

  7. 用dup2和dup产生一份file descriptor 的拷贝

    在类Unix操作系统里面,.dup2和dup都通过系统调用来产生一份file descriptor 的拷贝.     dup对我来说还很简单     int dup(int filedes);     ...

  8. mysql 文件描述符_MySQL异常探究:File Descriptor xxxx exceeded FD_SETSIZE=xxxx

    异常LOG 问题表象 表象为:在连接数据库时报以下错误: The last packet sent successfully to the server was 0 milliseconds ago ...

  9. File Descriptor问题总结

    今天客户物理机上遇到文件描述符用尽的问题,现象包括: SSH连接物理机卡住 PG服务端口TCP心跳检测失败 PSQL卡住 报错:too many open files 概念 在Linux系统中一切皆可 ...

最新文章

  1. python语言基础汇总
  2. 数字经济时代,什么是关键资源?(算力篇)
  3. 用服务器安装nginx部署web页面
  4. python交并补_Python 集合的交差并补操作及方法
  5. EDM邮件群发十大技巧提升邮件群发效果
  6. 使用逻辑回归制作评分卡
  7. cdn dashjs_CSS以及JS各种库的在线CDN引用地址
  8. npoi 所有列调整为一页_Word节约纸张打印 多页内容一页打印
  9. assoc fetch mysql 用法_mysql_fetch_assoc、mysql_fetch_object、mysql_fetch_row、mysql_fetch_array用法学习...
  10. php是视频还是图片格式,php 视频、音频和图片文件上传,该如何解决
  11. Spring依赖注入static静态变量相关问题
  12. android屏幕操作提示音,快捷指令库提示音
  13. 5个免费可商用的图片素材网站,赶快收藏
  14. 防盗系统Java_java小区防盗报警系统
  15. Python微信防撤回,基于itchat模块
  16. 为何延时函数不起作用?
  17. 华为手机word插件加载失败_c#调用word的组件时失败解决方法
  18. 数学之美-读书笔记6-10章
  19. CSP 202206-1 归一化处理
  20. 北京市道路街道区县shape分享

热门文章

  1. python调用winrar解压_批量文件解压缩脚本(Python3.5 + WinRAR)
  2. php 删除mysql 返回_php 返回mysql字符编码与删除字符编码
  3. webpack 4.0 小记
  4. Spring Cloud微服务系列文,服务调用框架Feign
  5. Mac下crontab -e没结果的解决办法
  6. 柯南君:看大数据时代下的IT架构(4)消息队列之RabbitMQ--案例(Helloword起航)...
  7. 接口隔离原则(设计模式4)
  8. C++虚函数与虚函数表
  9. 分享字符串右移的算法
  10. Windows Phone MultiBinding :Cimbalino Toolkit