偶尔的“切片超出范围”恐慌

Posted

技术标签:

【中文标题】偶尔的“切片超出范围”恐慌【英文标题】:Occasional 'slice bounds out of range' panic 【发布时间】:2021-07-22 02:04:20 【问题描述】:

我正在运行一个将 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

我不明白为什么迭代连接并附加它们有时会引发恐慌。

【问题讨论】:

在错误行中,切片索引正在增加。它可能会超出 slice 的上限并引发恐慌。 slice 的上限是什么? 谢谢!运行竞争检测器并设法用互斥锁修复它。 【参考方案1】:

我想说的几点:

    确保您的程序没有竞争条件(例如,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:]...)
    

在示例中,您可以看到您尝试访问它的索引大于或等于触发恐慌的切片的长度。我认为这个answer 应该可以帮助您纠正您的逻辑,或者您也可以使用地图来存储连接,但它再次带有自己的警告,例如没有顺序保证,即它从地图中读取的顺序。

【讨论】:

非常感谢您抽出宝贵时间回复!我已经在我的代码中实现了它,到目前为止不再恐慌。我最终用 Mutex 修复了比赛。如果同时转发了很多 webhook,它可能会减慢执行速度,所以我在想是否有比 map 更好的东西来保存所有可以同时处理读/写而没有锁的客户端。无论如何,这行得通,将其标记为已解决! 但也要确保修复append;因为这可能是后来 WriteJSON 失败的原因。如果您有大量客户,也许您可​​以使用快速的 KV 存储,例如 badger (github.com/dgraph-io/badger) 或分片地图或类似的东西,看看是否有帮助。是的,做基准测试。 是的,我也修复了附加!到目前为止,过去 20 小时内不再出现恐慌。会看看badger,谢谢你的建议! 伟大的@Ephebopus!

以上是关于偶尔的“切片超出范围”恐慌的主要内容,如果未能解决你的问题,请参考以下文章

Python中偶尔遇到的细节疑问:去除列名特殊字符标准差出现nan切片索引可超出范围range步长

linux进不了,提示信号超出范围。

分配对局部变量的引用,如果局部变量超出范围,它会超出范围吗?

对于char型的,如果超出范围怎样算?

对于char型的,如果超出范围怎样算?

JsLint'超出范围'错误