当前位置: 首页 > news >正文

【Go】Gin 超时中间件的坑:fatal error: concurrent map writes

Gin 社区超时中间件的坑:导致线上 Pod 异常重启

在最近的项目中,我们遇到了因为 Gin 超时中间件(timeout 引发的生产事故:Pod 异常退出并重启

问题现场

pod无故重启,抓取标准输出日志,问题指向超时中间件
在这里插入图片描述

堆栈报错信息如下
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

为什么会并发写入呢? 报错指向Go社区的超时中间件,社区搜索相关issue, 果然有相关问题 ttps://github.com/gin-contrib/timeout/pull/55

我们的代码封装

func timeoutMiddleWare(timeoutInt int) gin.HandlerFunc {return timeout.New(timeout.WithTimeout(time.Duration(timeoutInt)*time.Second),timeout.WithResponse(func(c *gin.Context) {c.JSON(http.StatusGatewayTimeout, response.Failed(http.StatusGatewayTimeout, nil))}),)
}

问题复现与成因

先说原因:

超时中间件额外开了一个协程去执行业务逻辑,超时中间件的逻辑在另外的的协程中,当请求超时发生时会出现了两个 goroutine 同时对响应进行写操作,而gin的源码响应中有写入map的操作,这会导致 重复写入,并触发 map 并发写 错误(Go 的 map 在并发写时会直接 panic), 从而导致Pod 异常退出,K8s 会立刻重启容器。

源码分析:

// github.com/gin-contrib/timeout v1.0.1
var bufPool *BufferPool
const (defaultTimeout = 5 * time.Second
)
// New wraps a handler and aborts the process of the handler if the timeout is reached
func New(opts ...Option) gin.HandlerFunc {t := &Timeout{timeout:  defaultTimeout,handler:  nil,response: defaultResponse,}// Loop through each optionfor _, opt := range opts {if opt == nil {panic("timeout Option not be nil")}// Call the option giving the instantiatedopt(t)}if t.timeout <= 0 {return t.handler}bufPool = &BufferPool{}return func(c *gin.Context) {finish := make(chan struct{}, 1)panicChan := make(chan interface{}, 1)w := c.Writerbuffer := bufPool.Get()tw := NewWriter(w, buffer)c.Writer = twbuffer.Reset()// 这里开了一个协程去执行业务逻辑go func() {defer func() {if p := recover(); p != nil {panicChan <- p}}()t.handler(c)finish <- struct{}{}}()select {case p := <-panicChan:tw.FreeBuffer()c.Writer = wpanic(p)case <-finish:c.Next()tw.mu.Lock()defer tw.mu.Unlock()dst := tw.ResponseWriter.Header()for k, vv := range tw.Header() {dst[k] = vv}tw.ResponseWriter.WriteHeader(tw.code)if _, err := tw.ResponseWriter.Write(buffer.Bytes()); err != nil {panic(err)}tw.FreeBuffer()bufPool.Put(buffer)case <-time.After(t.timeout):c.Abort()tw.mu.Lock()defer tw.mu.Unlock()tw.timeout = truetw.FreeBuffer()bufPool.Put(buffer)// v1.0.1 报错的代码c.Writer = wt.response(c)c.Writer = tw// v1.1.0 修复后的PR代码cc := c.Copy() // 重新拷贝了一份gin.Context进行响应cc.Writer = wt.response(cc)}}// t.response 实际是调用gin.Context.String()
func defaultResponse(c *gin.Context) {c.String(http.StatusRequestTimeout, http.StatusText(http.StatusRequestTimeout))
}// gin源码 v1.10.0:
func (c *Context) String(code int, format string, values ...interface{}) {c.Render(code, render.String{Format: format, Data: values})
}// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) {c.Status(code)if !bodyAllowedForStatus(code) {// 关键在这里 r.WriteContentType(c.Writer)c.Writer.WriteHeaderNow()return}if err := r.Render(c.Writer); err != nil {panic(err)}
}// WriteContentType (JSON) writes JSON ContentType.
func (r JSON) WriteContentType(w http.ResponseWriter) {writeContentType(w, jsonContentType)
}// !!!WriteContentType 最终会往header(map)中写入值,引发并发问题 !!!func writeContentType(w http.ResponseWriter, value []string) {header := w.Header()if val := header["Content-Type"]; len(val) == 0 {header["Content-Type"] = value}
}

社区修复

修复详情相关 PR:fix(response): conflict when handler completed and concurrent map writes by demouth · Pull Request #

在这里插入图片描述

解决办法

所有使用 go get github.com/gin-contrib/timeout 的项目需要升级:

  • Go 版本 ≥ 1.23.0
  • 拉取最新包: go get github.com/gin-contrib/timeout@v1.1.0
http://www.xdnf.cn/news/1286785.html

相关文章:

  • iOS 编译 cpp 代码生成 .a 库备忘
  • 医美产业科技成果展陈中心:连接微观肌肤世界与前沿科技的桥梁
  • 微算法科技(NASDAQ:MLGO)开发经典增强量子优化算法(CBQOA):开创组合优化新时代
  • 非凸科技受邀参加Community Over Code Asia 2025 Rust分论坛
  • 云计算分类与主流产品
  • 【论文阅读】一种基于经典机器学习的肌电下肢意图检测方法,用于人机交互系统
  • 【Node.js从 0 到 1:入门实战与项目驱动】2.1 安装 Node.js 与 npm(Windows/macOS/Linux 系统的安装步骤)
  • 网络基础设施保护
  • python题目练习 是否所有1都至少相隔k个元素 简单类型
  • 开博尔DA5耳放小尾巴体验评测:实体按键给到位,便携HiFi上手挺好用的
  • 25C机场航班调度程序(JS 100)
  • Ansible 基础到实操笔记
  • MySQL数据库操作全指南:数据库命令、表命令与数据CRUD操作
  • 飞算 JavaAI -智慧城市项目实践:从交通协同到应急响应的全链路技术革新
  • vue excel转json功能 xlsx
  • 正则表达式解析(二)
  • Python 标准库模块shutil
  • 升级 Docker,避免执行 docker compose 时报错
  • Java 大视界 -- Java 大数据在智能教育学习效果评估指标体系构建与精准评估中的应用(394)
  • 实现一个二维码让 iOS 和 Android 用户自动跳转到对应下载链接
  • MySQL——MySQL引擎层BufferPool工作过程原理
  • 3 Abp 核心框架(Core Framework)
  • 京东方 DV133FHM-NN1 FHD13.3寸 工业液晶模组技术档案
  • 在Colab上复现LoRA相关论文实验的完整指南
  • 跨设备开发不再难:HarmonyOS 分布式任务管理应用全解析
  • 人机交互:连接人类与数字世界的桥梁
  • 基于柔性管控终端的新能源汽车充电站有序充电系统设计与实现
  • Docker容器部署论坛和网上商城(小白的“升级打怪”成长之路)
  • 1 JQ6500语音播报模块详解(STM32)
  • 电机极数2极、4极、6极、8极的区别