go - 偶尔的“切片超出范围”恐慌
问题描述
我正在运行一个将 webhook 转发到 WebSocket 的脚本。将 webhook 发送到 WebSocket 的部分会检查非活动连接并在转发 webhook 时尝试删除它们有时会失败并出现以下错误:
http: panic serving 10.244.38.169:40958: runtime error: slice bounds out of range
(IP/端口总是不同的,这只是一个例子。)
相关代码:
// Map holding all Websocket clients and the endpoints they are subscribed to
var clients = make(map[string][]*websocket.Conn)
var upgrader = websocket.Upgrader{}
// function to execute when a new client connects to the websocket
func handleClient(w http.ResponseWriter, r *http.Request, endpoint string) {
conn, err := upgrader.Upgrade(w, r, nil)
// ...
// Add client to endpoint slice
clients[endpoint] = append(clients[endpoint], conn)
}
// function to send a webhook to a websocket endpoint
func handleHook(w http.ResponseWriter, r *http.Request, endpoint string) {
msg := Message{}
// ...
// Get all clients listening to the current endpoint
conns := clients[endpoint]
if conns != nil {
for i, conn := range conns {
if conn.WriteJSON(msg) != nil {
// Remove client and close connection if sending failed
conns = append(conns[:i], conns[i+1:]...) // this is the line that sometimes triggers the panic
conn.Close()
}
}
}
clients[endpoint] = conns
}
我无法弄清楚为什么迭代连接并附加它们有时会引发恐慌。
解决方案
我想说的几点:
确保您的程序没有竞争条件(例如,
clients
可全局访问,如果同时发生读/写或写/写,则应受到保护)。在切片上进行范围时,
for [...] range [...]
您不需要检查是否将非零切片作为范围句柄(请参阅我共享的代码)。它有时会发生在您身上,因为有时会
conn.WriteJSON
失败并返回错误,并且在范围内删除元素的错误逻辑会使您的程序恐慌。(见我分享的代码)
package main
import "fmt"
func main() {
var conns []string = nil
// "if conns != nil" check is not required as "for [...] range [...]"
// can handle that. It is safe to use for "range" directly.
for i, conn := range conns {
fmt.Println(i, conn)
}
conns = []string{"1", "2", "3"}
// Will panic
for i := range conns {
fmt.Printf("access: %d, length: %d\n", i, len(conns))
conns = append(conns[:i], conns[i+1:]...)
}
}
在示例中,您可以看到您尝试访问它的索引大于或等于触发恐慌的切片的长度。我认为这个答案应该可以帮助您纠正您的逻辑,或者您也可以使用地图来存储连接,但它再次带有自己的警告,例如没有顺序保证,即它从地图中读取的顺序。
推荐阅读
- orm - 在 sequelize 返回值中删除连接表数据
- python-3.x - 在 Selenium 中找到一个链接元素,该元素在其 href 中包含一个特定的单词与 python
- c++ - 尝试用分隔符连接 c 字符串数组
- javascript - 在angular9中的ngOnInit中没有调用服务?
- c# - Unity 无法退出玩家模组 (ZMQ)
- flutter - 如何使用 video_player 和 Chewie 正确赶上直播中的最新位置?
- javascript - 如何防止一个模板显示缓存值?
- python - Python - 为什么timedelta64值在excel中显示为0
- r - 如何在 R 中将 2 行或更多行文本合并为 1 行条件
- javascript - 在使用谷歌应用脚本将数据从谷歌表格注入谷歌幻灯片期间将日期转换为 dd/mm/yyyy